1use std::collections::HashMap;
2use super::{
3 oauth::AuthGrant,
4 permissions::{FinePermission, SecondaryPermission},
5};
6use serde::{Deserialize, Serialize};
7use totp_rs::TOTP;
8use tetratto_shared::{
9 hash::{hash_salted, salt},
10 snow::Snowflake,
11 unix_epoch_timestamp,
12};
13
14pub type Token = (String, String, usize);
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
18pub struct User {
19 pub id: usize,
20 pub created: usize,
21 pub username: String,
22 pub password: String,
23 pub salt: String,
24 pub settings: UserSettings,
25 pub tokens: Vec<Token>,
26 pub permissions: FinePermission,
27 pub is_verified: bool,
28 pub notification_count: usize,
29 pub follower_count: usize,
30 pub following_count: usize,
31 pub last_seen: usize,
32 #[serde(default)]
34 pub totp: String,
35 #[serde(default)]
37 pub recovery_codes: Vec<String>,
38 #[serde(default)]
39 pub post_count: usize,
40 #[serde(default)]
41 pub request_count: usize,
42 #[serde(default)]
44 pub connections: UserConnections,
45 #[serde(default)]
47 pub stripe_id: String,
48 #[serde(default)]
50 pub grants: Vec<AuthGrant>,
51 #[serde(default)]
53 pub associated: Vec<usize>,
54 #[serde(default)]
56 pub invite_code: usize,
57 #[serde(default)]
59 pub secondary_permissions: SecondaryPermission,
60 #[serde(default)]
62 pub achievements: Vec<Achievement>,
63 #[serde(default)]
66 pub awaiting_purchase: bool,
67 #[serde(default)]
71 pub was_purchased: bool,
72 #[serde(default)]
81 pub browser_session: String,
82 #[serde(default)]
84 pub ban_reason: String,
85 #[serde(default)]
87 pub channel_mutes: Vec<usize>,
88 #[serde(default)]
91 pub is_deactivated: bool,
92 #[serde(default)]
94 pub ban_expire: usize,
95 #[serde(default)]
97 pub coins: i32,
98 #[serde(default)]
103 pub checkouts: Vec<String>,
104 #[serde(default)]
106 pub applied_configurations: Vec<(AppliedConfigType, usize)>,
107 #[serde(default)]
109 pub last_policy_consent: usize,
110}
111
112pub type UserConnections =
113 HashMap<ConnectionService, (ExternalConnectionInfo, ExternalConnectionData)>;
114
115#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
116pub enum AppliedConfigType {
117 StyleSnippet,
119}
120
121#[derive(Clone, Debug, Serialize, Deserialize)]
122pub enum ThemePreference {
123 Auto,
124 Dark,
125 Light,
126}
127
128impl Default for ThemePreference {
129 fn default() -> Self {
130 Self::Auto
131 }
132}
133
134#[derive(Clone, Debug, Serialize, Deserialize)]
135pub enum DefaultTimelineChoice {
136 MyCommunities,
137 MyCommunitiesQuestions,
138 PopularPosts,
139 PopularQuestions,
140 FollowingPosts,
141 FollowingQuestions,
142 AllPosts,
143 AllQuestions,
144 Stack(String),
145}
146
147impl Default for DefaultTimelineChoice {
148 fn default() -> Self {
149 Self::MyCommunities
150 }
151}
152
153impl DefaultTimelineChoice {
154 pub fn relative_url(&self) -> String {
156 match &self {
157 Self::MyCommunities => "/".to_string(),
158 Self::MyCommunitiesQuestions => "/questions".to_string(),
159 Self::PopularPosts => "/popular".to_string(),
160 Self::PopularQuestions => "/popular/questions".to_string(),
161 Self::FollowingPosts => "/following".to_string(),
162 Self::FollowingQuestions => "/following/questions".to_string(),
163 Self::AllPosts => "/all".to_string(),
164 Self::AllQuestions => "/all/questions".to_string(),
165 Self::Stack(id) => format!("/stacks/{id}"),
166 }
167 }
168}
169
170#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
171pub enum DefaultProfileTabChoice {
172 Posts,
174 Responses,
176}
177
178impl Default for DefaultProfileTabChoice {
179 fn default() -> Self {
180 Self::Posts
181 }
182}
183
184#[derive(Clone, Debug, Serialize, Deserialize, Default)]
185pub struct UserSettings {
186 #[serde(default)]
187 pub display_name: String,
188 #[serde(default)]
189 pub biography: String,
190 #[serde(default)]
191 pub warning: String,
192 #[serde(default)]
193 pub private_profile: bool,
194 #[serde(default)]
195 pub private_communities: bool,
196 #[serde(default)]
198 pub theme_preference: ThemePreference,
199 #[serde(default)]
202 pub profile_theme: ThemePreference,
203 #[serde(default)]
204 pub private_last_seen: bool,
205 #[serde(default)]
206 pub theme_hue: String,
207 #[serde(default)]
208 pub theme_sat: String,
209 #[serde(default)]
210 pub theme_lit: String,
211 #[serde(default)]
213 pub theme_color_surface: String,
214 #[serde(default)]
216 pub theme_color_text: String,
217 #[serde(default)]
219 pub theme_color_text_link: String,
220 #[serde(default)]
222 pub theme_color_lowered: String,
223 #[serde(default)]
225 pub theme_color_text_lowered: String,
226 #[serde(default)]
228 pub theme_color_super_lowered: String,
229 #[serde(default)]
231 pub theme_color_raised: String,
232 #[serde(default)]
234 pub theme_color_text_raised: String,
235 #[serde(default)]
237 pub theme_color_super_raised: String,
238 #[serde(default)]
240 pub theme_color_primary: String,
241 #[serde(default)]
243 pub theme_color_text_primary: String,
244 #[serde(default)]
246 pub theme_color_primary_lowered: String,
247 #[serde(default)]
249 pub theme_color_secondary: String,
250 #[serde(default)]
252 pub theme_color_text_secondary: String,
253 #[serde(default)]
255 pub theme_color_secondary_lowered: String,
256 #[serde(default)]
258 pub theme_custom_css: String,
259 #[serde(default)]
261 pub theme_color_online: String,
262 #[serde(default)]
264 pub theme_color_idle: String,
265 #[serde(default)]
267 pub theme_color_offline: String,
268 #[serde(default)]
269 pub disable_other_themes: bool,
270 #[serde(default)]
271 pub disable_other_theme_css: bool,
272 #[serde(default)]
273 pub enable_questions: bool,
274 #[serde(default)]
276 pub motivational_header: String,
277 #[serde(default)]
279 pub allow_anonymous_questions: bool,
280 #[serde(default)]
282 pub anonymous_username: String,
283 #[serde(default)]
285 pub anonymous_avatar_url: String,
286 #[serde(default)]
288 pub hide_dislikes: bool,
289 #[serde(default)]
291 pub default_timeline: DefaultTimelineChoice,
292 #[serde(default)]
294 pub private_chats: bool,
295 #[serde(default)]
297 pub private_mails: bool,
298 #[serde(default)]
300 pub status: String,
301 #[serde(default = "mime_avif")]
303 pub avatar_mime: String,
304 #[serde(default = "mime_avif")]
306 pub banner_mime: String,
307 #[serde(default)]
309 pub require_account: bool,
310 #[serde(default)]
312 pub show_nsfw: bool,
313 #[serde(default)]
315 pub hide_extra_post_tabs: bool,
316 #[serde(default)]
318 pub muted: Vec<String>,
319 #[serde(default)]
321 pub paged_timelines: bool,
322 #[serde(default)]
324 pub enable_drawings: bool,
325 #[serde(default)]
327 pub auto_unlist: bool,
328 #[serde(default)]
330 pub all_timeline_hide_answers: bool,
331 #[serde(default)]
333 pub auto_clear_notifs: bool,
334 #[serde(default)]
336 pub large_text: bool,
337 #[serde(default)]
339 pub disable_achievements: bool,
340 #[serde(default)]
342 pub hide_associated_blocked_users: bool,
343 #[serde(default)]
345 pub default_profile_tab: DefaultProfileTabChoice,
346 #[serde(default)]
351 pub hide_from_social_lists: bool,
352 #[serde(default)]
355 pub auto_full_unlist: bool,
356 #[serde(default)]
358 pub private_biography: String,
359 #[serde(default)]
362 pub hide_social_follows: bool,
363 #[serde(default)]
365 pub mail_signature: String,
366 #[serde(default)]
368 pub forum_signature: String,
369 #[serde(default)]
371 pub no_transfers: bool,
372 #[serde(default)]
374 pub enable_shop: bool,
375 #[serde(default)]
377 pub hide_username_badges: bool,
378}
379
380fn mime_avif() -> String {
381 "image/avif".to_string()
382}
383
384impl Default for User {
385 fn default() -> Self {
386 Self::new("<unknown>".to_string(), String::new())
387 }
388}
389
390impl User {
391 pub fn new(username: String, password: String) -> Self {
393 let salt = salt();
394 let password = hash_salted(password, salt.clone());
395 let created = unix_epoch_timestamp();
396
397 Self {
398 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
399 created,
400 username,
401 password,
402 salt,
403 settings: UserSettings::default(),
404 tokens: Vec::new(),
405 permissions: FinePermission::DEFAULT,
406 is_verified: false,
407 notification_count: 0,
408 follower_count: 0,
409 following_count: 0,
410 last_seen: created,
411 totp: String::new(),
412 recovery_codes: Vec::new(),
413 post_count: 0,
414 request_count: 0,
415 connections: HashMap::new(),
416 stripe_id: String::new(),
417 grants: Vec::new(),
418 associated: Vec::new(),
419 invite_code: 0,
420 secondary_permissions: SecondaryPermission::DEFAULT,
421 achievements: Vec::new(),
422 awaiting_purchase: false,
423 was_purchased: false,
424 browser_session: String::new(),
425 ban_reason: String::new(),
426 channel_mutes: Vec::new(),
427 is_deactivated: false,
428 ban_expire: 0,
429 coins: 0,
430 checkouts: Vec::new(),
431 applied_configurations: Vec::new(),
432 last_policy_consent: created,
433 }
434 }
435
436 pub fn deleted() -> Self {
438 Self {
439 username: "<deleted>".to_string(),
440 id: 0,
441 ..Default::default()
442 }
443 }
444
445 pub fn banned() -> Self {
447 Self {
448 username: "<banned>".to_string(),
449 id: 0,
450 ..Default::default()
451 }
452 }
453
454 pub fn anonymous() -> Self {
456 Self {
457 username: "anonymous".to_string(),
458 id: 0,
459 ..Default::default()
460 }
461 }
462
463 pub fn create_token(ip: &str) -> (String, Token) {
468 let unhashed = tetratto_shared::hash::uuid();
469 (
470 unhashed.clone(),
471 (
472 ip.to_string(),
473 tetratto_shared::hash::hash(unhashed),
474 unix_epoch_timestamp(),
475 ),
476 )
477 }
478
479 pub fn check_password(&self, against: String) -> bool {
481 self.password == hash_salted(against, self.salt.clone())
482 }
483
484 pub fn parse_mentions(input: &str) -> Vec<String> {
486 let mut escape: bool = false;
488 let mut at: bool = false;
489 let mut buffer: String = String::new();
490 let mut out = Vec::new();
491
492 for char in input.chars() {
494 if ((char == '\\') | (char == '/')) && !escape {
495 escape = true;
496 continue;
497 }
498
499 if (char == '@') && !escape {
500 at = true;
501 continue; }
503
504 if at {
505 if char == ' ' {
506 at = false;
508
509 if !out.contains(&buffer) {
510 out.push(buffer);
511 }
512
513 buffer = String::new();
514 continue;
515 }
516
517 buffer.push(char);
519 }
520
521 escape = false;
522 }
523
524 if !buffer.is_empty() {
525 out.push(buffer);
526 }
527
528 if out.len() > 5 {
529 return Vec::new();
531 }
532
533 out
535 }
536
537 pub fn totp(&self, issuer: Option<String>) -> Option<TOTP> {
539 if self.totp.is_empty() {
540 return None;
541 }
542
543 TOTP::new(
544 totp_rs::Algorithm::SHA1,
545 6,
546 1,
547 30,
548 self.totp.as_bytes().to_owned(),
549 Some(issuer.unwrap_or("tetratto!".to_string())),
550 self.username.clone(),
551 )
552 .ok()
553 }
554
555 pub fn clean(&mut self) {
557 self.password = String::new();
558 self.salt = String::new();
559
560 self.tokens = Vec::new();
561 self.grants = Vec::new();
562
563 self.recovery_codes = Vec::new();
564 self.totp = String::new();
565
566 self.settings = UserSettings::default();
567 self.stripe_id = String::new();
568 self.connections = HashMap::new();
569 }
570
571 pub fn get_grant_by_app_id(&self, id: usize) -> Option<&AuthGrant> {
576 self.grants.iter().find(|x| x.app == id)
577 }
578}
579
580#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
581pub enum ConnectionService {
582 Spotify,
584 LastFm,
586}
587
588#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
589pub enum ConnectionType {
590 Token,
592 PKCE,
594 None,
596}
597
598#[derive(Clone, Debug, Serialize, Deserialize)]
599pub struct ExternalConnectionInfo {
600 pub con_type: ConnectionType,
601 pub data: HashMap<String, String>,
602 pub show_on_profile: bool,
603}
604
605#[derive(Clone, Debug, Serialize, Deserialize, Default)]
606pub struct ExternalConnectionData {
607 pub external_urls: HashMap<String, String>,
608 pub data: HashMap<String, String>,
609}
610
611pub const ACHIEVEMENTS: usize = 36;
613pub const SELF_SERVE_ACHIEVEMENTS: &[AchievementName] = &[
615 AchievementName::OpenReference,
616 AchievementName::OpenTos,
617 AchievementName::OpenPrivacyPolicy,
618 AchievementName::AcceptProfileWarning,
619 AchievementName::OpenSessionSettings,
620];
621
622#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
623pub enum AchievementName {
624 CreatePost,
625 FollowUser,
626 Create50Posts,
627 Create100Posts,
628 Create1000Posts,
629 CreateQuestion,
630 EditSettings,
631 CreateJournal,
632 FollowedByStaff,
633 CreateDrawing,
634 OpenAchievements,
635 Get1Like,
636 Get10Likes,
637 Get50Likes,
638 Get100Likes,
639 Get25Dislikes,
640 Get1Follower,
641 Get10Followers,
642 Get50Followers,
643 Get100Followers,
644 Follow10Users,
645 JoinCommunity,
646 CreateDraft,
647 EditPost,
648 Enable2fa,
649 EditNote,
650 CreatePostWithTitle,
651 CreateRepost,
652 OpenTos,
653 OpenPrivacyPolicy,
654 OpenReference,
655 GetAllOtherAchievements,
656 AcceptProfileWarning,
657 OpenSessionSettings,
658 CreateSite,
659 CreateDomain,
660}
661
662#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
663pub enum AchievementRarity {
664 Common,
665 Uncommon,
666 Rare,
667}
668
669impl AchievementName {
670 pub fn title(&self) -> &str {
671 match self {
672 Self::CreatePost => "Dear friends,",
673 Self::FollowUser => "Virtual connections...",
674 Self::Create50Posts => "Hello, world!",
675 Self::Create100Posts => "It's my world",
676 Self::Create1000Posts => "Timeline domination",
677 Self::CreateQuestion => "Big questions...",
678 Self::EditSettings => "Just how I like it!",
679 Self::CreateJournal => "Dear diary...",
680 Self::FollowedByStaff => "Big Shrimpin'",
681 Self::CreateDrawing => "Modern art",
682 Self::OpenAchievements => "Welcome!",
683 Self::Get1Like => "Baby steps!",
684 Self::Get10Likes => "WOW! 10 LIKES!",
685 Self::Get50Likes => "banger post follow for more",
686 Self::Get100Likes => "everyone liked that",
687 Self::Get25Dislikes => "Sorry...",
688 Self::Get1Follower => "Friends?",
689 Self::Get10Followers => "Friends!",
690 Self::Get50Followers => "50 WHOLE FOLLOWERS??",
691 Self::Get100Followers => "Everyone is my friend!",
692 Self::Follow10Users => "Big fan",
693 Self::JoinCommunity => "A sense of community...",
694 Self::CreateDraft => "Maybe later!",
695 Self::EditPost => "Grammar police?",
696 Self::Enable2fa => "Locked in",
697 Self::EditNote => "I take it back!",
698 Self::CreatePostWithTitle => "Must declutter",
699 Self::CreateRepost => "More than a like or comment...",
700 Self::OpenTos => "Well informed!",
701 Self::OpenPrivacyPolicy => "Privacy conscious",
702 Self::OpenReference => "What does this do?",
703 Self::GetAllOtherAchievements => "The final performance",
704 Self::AcceptProfileWarning => "I accept the risks!",
705 Self::OpenSessionSettings => "Am I alone in here?",
706 Self::CreateSite => "Littlewebmaster",
707 Self::CreateDomain => "LittleDNS",
708 }
709 }
710
711 pub fn description(&self) -> &str {
712 match self {
713 Self::CreatePost => "Create your first post!",
714 Self::FollowUser => "Follow somebody!",
715 Self::Create50Posts => "Create your 50th post.",
716 Self::Create100Posts => "Create your 100th post.",
717 Self::Create1000Posts => "Create your 1000th post.",
718 Self::CreateQuestion => "Ask your first question!",
719 Self::EditSettings => "Edit your settings.",
720 Self::CreateJournal => "Create your first journal.",
721 Self::FollowedByStaff => "Get followed by a staff member!",
722 Self::CreateDrawing => "Include a drawing in a question.",
723 Self::OpenAchievements => "Open the achievements page.",
724 Self::Get1Like => "Get 1 like on a post! Good job!",
725 Self::Get10Likes => "Get 10 likes on one post.",
726 Self::Get50Likes => "Get 50 likes on one post.",
727 Self::Get100Likes => "Get 100 likes on one post.",
728 Self::Get25Dislikes => "Get 25 dislikes on one post... :(",
729 Self::Get1Follower => "Get 1 follower. Cool!",
730 Self::Get10Followers => "Get 10 followers. You're getting popular!",
731 Self::Get50Followers => "Get 50 followers. Okay, you're fairly popular!",
732 Self::Get100Followers => "Get 100 followers. You might be famous..?",
733 Self::Follow10Users => "Follow 10 other users. I'm sure people appreciate it!",
734 Self::JoinCommunity => "Join a community. Welcome!",
735 Self::CreateDraft => "Save a post as a draft.",
736 Self::EditPost => "Edit a post.",
737 Self::Enable2fa => "Enable TOTP 2FA.",
738 Self::EditNote => "Edit a note.",
739 Self::CreatePostWithTitle => "Create a post with a title.",
740 Self::CreateRepost => "Create a repost or quote.",
741 Self::OpenTos => "Open the terms of service.",
742 Self::OpenPrivacyPolicy => "Open the privacy policy.",
743 Self::OpenReference => "Open the source code reference documentation.",
744 Self::GetAllOtherAchievements => "Get every other achievement.",
745 Self::AcceptProfileWarning => "Accept a profile warning.",
746 Self::OpenSessionSettings => "Open your session settings.",
747 Self::CreateSite => "Create a site.",
748 Self::CreateDomain => "Create a domain.",
749 }
750 }
751
752 pub fn rarity(&self) -> AchievementRarity {
753 use AchievementRarity::*;
755 match self {
756 Self::CreatePost => Common,
757 Self::FollowUser => Common,
758 Self::Create50Posts => Uncommon,
759 Self::Create100Posts => Uncommon,
760 Self::Create1000Posts => Rare,
761 Self::CreateQuestion => Common,
762 Self::EditSettings => Common,
763 Self::CreateJournal => Uncommon,
764 Self::FollowedByStaff => Rare,
765 Self::CreateDrawing => Common,
766 Self::OpenAchievements => Common,
767 Self::Get1Like => Common,
768 Self::Get10Likes => Common,
769 Self::Get50Likes => Uncommon,
770 Self::Get100Likes => Rare,
771 Self::Get25Dislikes => Uncommon,
772 Self::Get1Follower => Common,
773 Self::Get10Followers => Common,
774 Self::Get50Followers => Uncommon,
775 Self::Get100Followers => Rare,
776 Self::Follow10Users => Common,
777 Self::JoinCommunity => Common,
778 Self::CreateDraft => Common,
779 Self::EditPost => Common,
780 Self::Enable2fa => Rare,
781 Self::EditNote => Uncommon,
782 Self::CreatePostWithTitle => Common,
783 Self::CreateRepost => Common,
784 Self::OpenTos => Uncommon,
785 Self::OpenPrivacyPolicy => Uncommon,
786 Self::OpenReference => Uncommon,
787 Self::GetAllOtherAchievements => Rare,
788 Self::AcceptProfileWarning => Common,
789 Self::OpenSessionSettings => Common,
790 Self::CreateSite => Common,
791 Self::CreateDomain => Common,
792 }
793 }
794}
795
796impl Into<Achievement> for AchievementName {
797 fn into(self) -> Achievement {
798 Achievement {
799 name: self,
800 unlocked: unix_epoch_timestamp(),
801 }
802 }
803}
804
805#[derive(Clone, Debug, Serialize, Deserialize)]
806pub struct Achievement {
807 pub name: AchievementName,
808 pub unlocked: usize,
809}
810
811#[derive(Debug, Serialize, Deserialize)]
812pub struct Notification {
813 pub id: usize,
814 pub created: usize,
815 pub title: String,
816 pub content: String,
817 pub owner: usize,
818 pub read: bool,
819 pub tag: String,
820}
821
822impl Notification {
823 pub fn new(title: String, content: String, owner: usize) -> Self {
825 Self {
826 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
827 created: unix_epoch_timestamp(),
828 title,
829 content,
830 owner,
831 read: false,
832 tag: String::new(),
833 }
834 }
835}
836
837#[derive(Clone, Debug, Serialize, Deserialize)]
838pub struct UserFollow {
839 pub id: usize,
840 pub created: usize,
841 pub initiator: usize,
842 pub receiver: usize,
843}
844
845impl UserFollow {
846 pub fn new(initiator: usize, receiver: usize) -> Self {
848 Self {
849 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
850 created: unix_epoch_timestamp(),
851 initiator,
852 receiver,
853 }
854 }
855}
856
857#[derive(Serialize, Deserialize, PartialEq, Eq)]
858pub enum FollowResult {
859 Requested,
861 Followed,
863}
864
865#[derive(Serialize, Deserialize)]
866pub struct UserBlock {
867 pub id: usize,
868 pub created: usize,
869 pub initiator: usize,
870 pub receiver: usize,
871}
872
873impl UserBlock {
874 pub fn new(initiator: usize, receiver: usize) -> Self {
876 Self {
877 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
878 created: unix_epoch_timestamp(),
879 initiator,
880 receiver,
881 }
882 }
883}
884
885#[derive(Serialize, Deserialize)]
886pub struct IpBlock {
887 pub id: usize,
888 pub created: usize,
889 pub initiator: usize,
890 pub receiver: String,
891}
892
893impl IpBlock {
894 pub fn new(initiator: usize, receiver: String) -> Self {
896 Self {
897 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
898 created: unix_epoch_timestamp(),
899 initiator,
900 receiver,
901 }
902 }
903}
904
905#[derive(Serialize, Deserialize)]
906pub struct IpBan {
907 pub ip: String,
908 pub created: usize,
909 pub reason: String,
910 pub moderator: usize,
911}
912
913impl IpBan {
914 pub fn new(ip: String, moderator: usize, reason: String) -> Self {
916 Self {
917 ip,
918 created: unix_epoch_timestamp(),
919 reason,
920 moderator,
921 }
922 }
923}
924
925#[derive(Serialize, Deserialize)]
926pub struct UserWarning {
927 pub id: usize,
928 pub created: usize,
929 pub receiver: usize,
930 pub moderator: usize,
931 pub content: String,
932}
933
934impl UserWarning {
935 pub fn new(user: usize, moderator: usize, content: String) -> Self {
937 Self {
938 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
939 created: unix_epoch_timestamp(),
940 receiver: user,
941 moderator,
942 content,
943 }
944 }
945}
946
947#[derive(Clone, Debug, Serialize, Deserialize)]
948pub struct InviteCode {
949 pub id: usize,
950 pub created: usize,
951 pub owner: usize,
952 pub code: String,
953 pub is_used: bool,
954}
955
956impl InviteCode {
957 pub fn new(owner: usize) -> Self {
959 Self {
960 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
961 created: unix_epoch_timestamp(),
962 owner,
963 code: salt(),
964 is_used: false,
965 }
966 }
967}