1use std::collections::HashMap;
2use crate::model::{Error, Result};
3
4use super::{
5 oauth::AuthGrant,
6 permissions::{FinePermission, SecondaryPermission},
7};
8use serde::{Deserialize, Serialize};
9use totp_rs::TOTP;
10use tetratto_shared::{
11 hash::{hash_salted, salt},
12 snow::Snowflake,
13 unix_epoch_timestamp,
14};
15use serde_valid::Validate;
16
17pub type Token = (String, String, usize);
19
20#[derive(Clone, Debug, Serialize, Deserialize)]
21pub struct User {
22 pub id: usize,
23 pub created: usize,
24 pub username: String,
25 pub password: String,
26 pub salt: String,
27 pub settings: UserSettings,
28 pub tokens: Vec<Token>,
29 pub permissions: FinePermission,
30 pub is_verified: bool,
31 pub notification_count: usize,
32 pub follower_count: usize,
33 pub following_count: usize,
34 pub last_seen: usize,
35 #[serde(default)]
37 pub totp: String,
38 #[serde(default)]
40 pub recovery_codes: Vec<String>,
41 #[serde(default)]
42 pub post_count: usize,
43 #[serde(default)]
44 pub request_count: usize,
45 #[serde(default)]
47 pub connections: UserConnections,
48 #[serde(default)]
50 pub stripe_id: String,
51 #[serde(default)]
53 pub grants: Vec<AuthGrant>,
54 #[serde(default)]
56 pub associated: Vec<usize>,
57 #[serde(default)]
59 pub invite_code: usize,
60 #[serde(default)]
62 pub secondary_permissions: SecondaryPermission,
63 #[serde(default)]
65 pub achievements: Vec<Achievement>,
66 #[serde(default)]
69 pub awaiting_purchase: bool,
70 #[serde(default)]
74 pub was_purchased: bool,
75 #[serde(default)]
77 pub ban_reason: String,
78 #[serde(default)]
81 pub is_deactivated: bool,
82 #[serde(default)]
84 pub ban_expire: usize,
85 #[serde(default)]
90 pub checkouts: Vec<String>,
91 #[serde(default)]
93 pub last_policy_consent: usize,
94 #[serde(default)]
99 pub close_friends_stack: usize,
100 #[serde(default)]
102 pub missed_messages_count: usize,
103 #[serde(default)]
105 pub views: usize,
106 #[serde(default)]
108 pub shrimpcamp_link: usize,
109}
110
111pub type UserConnections =
112 HashMap<ConnectionService, (ExternalConnectionInfo, ExternalConnectionData)>;
113
114#[derive(Clone, Debug, Serialize, Deserialize, Default)]
115pub enum ThemePreference {
116 #[default]
117 Auto,
118 Dark,
119 Light,
120}
121
122#[derive(Clone, Debug, Serialize, Deserialize, Default)]
123pub enum DefaultTimelineChoice {
124 #[default]
125 MyCommunities,
126 MyCommunitiesQuestions,
127 PopularPosts,
128 PopularQuestions,
129 FollowingPosts,
130 FollowingQuestions,
131 AllPosts,
132 AllQuestions,
133 Stack(String),
134}
135
136impl DefaultTimelineChoice {
137 pub fn relative_url(&self) -> String {
139 match &self {
140 Self::MyCommunities => "/".to_string(),
141 Self::MyCommunitiesQuestions => "/questions".to_string(),
142 Self::PopularPosts => "/popular".to_string(),
143 Self::PopularQuestions => "/popular/questions".to_string(),
144 Self::FollowingPosts => "/following".to_string(),
145 Self::FollowingQuestions => "/following/questions".to_string(),
146 Self::AllPosts => "/all".to_string(),
147 Self::AllQuestions => "/all/questions".to_string(),
148 Self::Stack(id) => format!("/stacks/{id}"),
149 }
150 }
151}
152
153#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
154pub enum DefaultProfileTabChoice {
155 #[default]
157 Posts,
158 Responses,
160 Guestbook,
162}
163
164#[derive(Clone, Debug, Serialize, Deserialize, Default, Validate)]
165pub struct UserSettings {
166 #[serde(default)]
167 #[validate(max_length = 32)]
168 pub display_name: String,
169 #[serde(default)]
170 #[validate(max_length = 4096)]
171 pub biography: String,
172 #[serde(default)]
173 #[validate(max_length = 2048)]
174 pub warning: String,
175 #[serde(default)]
176 pub private_profile: bool,
177 #[serde(default)]
178 pub private_communities: bool,
179 #[serde(default)]
181 pub theme_preference: ThemePreference,
182 #[serde(default)]
185 pub profile_theme: ThemePreference,
186 #[serde(default)]
187 pub private_last_seen: bool,
188 #[serde(default)]
189 pub theme_hue: String,
190 #[serde(default)]
191 pub theme_sat: String,
192 #[serde(default)]
193 pub theme_lit: String,
194 #[serde(default)]
196 pub theme_color_surface: String,
197 #[serde(default)]
199 pub theme_color_text: String,
200 #[serde(default)]
202 pub theme_color_text_link: String,
203 #[serde(default)]
205 pub theme_color_shadow: String,
206 #[serde(default)]
208 pub theme_color_lowered: String,
209 #[serde(default)]
211 pub theme_color_text_lowered: String,
212 #[serde(default)]
214 pub theme_color_super_lowered: String,
215 #[serde(default)]
217 pub theme_color_raised: String,
218 #[serde(default)]
220 pub theme_color_text_raised: String,
221 #[serde(default)]
223 pub theme_color_super_raised: String,
224 #[serde(default)]
226 pub theme_color_primary: String,
227 #[serde(default)]
229 pub theme_color_text_primary: String,
230 #[serde(default)]
232 pub theme_color_primary_lowered: String,
233 #[serde(default)]
235 pub theme_color_secondary: String,
236 #[serde(default)]
238 pub theme_color_text_secondary: String,
239 #[serde(default)]
241 pub theme_color_secondary_lowered: String,
242 #[serde(default)]
244 pub theme_custom_css: String,
245 #[serde(default)]
247 pub theme_color_online: String,
248 #[serde(default)]
250 pub theme_color_idle: String,
251 #[serde(default)]
253 pub theme_color_offline: String,
254 #[serde(default)]
255 pub disable_other_themes: bool,
256 #[serde(default)]
257 pub disable_other_theme_css: bool,
258 #[serde(default)]
259 pub enable_questions: bool,
260 #[serde(default)]
262 pub motivational_header: String,
263 #[serde(default)]
265 pub allow_anonymous_questions: bool,
266 #[serde(default)]
268 pub anonymous_username: String,
269 #[serde(default)]
271 pub anonymous_avatar_url: String,
272 #[serde(default)]
274 pub hide_dislikes: bool,
275 #[serde(default)]
277 pub default_timeline: DefaultTimelineChoice,
278 #[serde(default)]
280 pub private_chats: bool,
281 #[serde(default)]
283 pub private_mails: bool,
284 #[serde(default)]
286 #[validate(max_length = 256)]
287 pub status: String,
288 #[serde(default = "mime_avif")]
290 pub banner_mime: String,
291 #[serde(default)]
293 pub require_account: bool,
294 #[serde(default)]
296 pub show_nsfw: bool,
297 #[serde(default)]
299 pub hide_extra_post_tabs: bool,
300 #[serde(default)]
302 pub muted: Vec<String>,
303 #[serde(default)]
305 pub paged_timelines: bool,
306 #[serde(default)]
308 pub enable_drawings: bool,
309 #[serde(default)]
311 pub auto_unlist: bool,
312 #[serde(default)]
314 pub all_timeline_hide_answers: bool,
315 #[serde(default)]
317 pub auto_clear_notifs: bool,
318 #[serde(default)]
320 pub large_text: bool,
321 #[serde(default)]
323 pub disable_achievements: bool,
324 #[serde(default)]
326 pub hide_associated_blocked_users: bool,
327 #[serde(default)]
329 pub default_profile_tab: DefaultProfileTabChoice,
330 #[serde(default)]
335 pub hide_from_social_lists: bool,
336 #[serde(default)]
339 pub auto_full_unlist: bool,
340 #[serde(default)]
342 pub private_biography: String,
343 #[serde(default)]
346 pub hide_social_follows: bool,
347 #[serde(default)]
349 #[validate(max_length = 2048)]
350 pub mail_signature: String,
351 #[serde(default)]
353 #[validate(max_length = 2048)]
354 pub forum_signature: String,
355 #[serde(default)]
357 pub hide_username_badges: bool,
358 #[serde(default)]
360 pub use_system_font: bool,
361 #[serde(default)]
364 #[validate(max_length = 128)]
365 pub location: String,
366 #[serde(default)]
368 #[validate(max_items = 15)]
369 #[validate(unique_items)]
370 pub links: Vec<(String, String)>,
371 #[serde(default)]
373 pub unround_corners: bool,
374 #[serde(default)]
376 pub enable_guestbook: bool,
377 #[serde(default)]
380 pub guestbook_require_review: bool,
381 #[serde(default)]
383 pub guestbook_motivational_header: String,
384 #[serde(default)]
388 pub simplified_profile: bool,
389 #[serde(default = "default_maximum_timeline_removed_percentage")]
391 pub maximum_timeline_removed_percentage: f64,
392 #[serde(default)]
394 pub disable_views: bool,
395}
396
397pub fn default_maximum_timeline_removed_percentage() -> f64 {
398 87.0
399}
400
401impl UserSettings {
402 pub fn verify_values(&self) -> Result<()> {
403 if let Err(e) = self.validate() {
404 return Err(Error::MiscError(e.to_string()));
405 }
406
407 Ok(())
408 }
409}
410
411fn mime_avif() -> String {
412 "image/avif".to_string()
413}
414
415impl Default for User {
416 fn default() -> Self {
417 Self::new("<unknown>".to_string(), String::new())
418 }
419}
420
421impl User {
422 pub fn new(username: String, password: String) -> Self {
424 let salt = salt();
425 let password = hash_salted(password, salt.clone());
426 let created = unix_epoch_timestamp();
427
428 Self {
429 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
430 created,
431 username,
432 password,
433 salt,
434 settings: {
435 let mut tmpl = UserSettings::default();
436 tmpl.maximum_timeline_removed_percentage =
437 default_maximum_timeline_removed_percentage();
438 tmpl
439 },
440 tokens: Vec::new(),
441 permissions: FinePermission::DEFAULT,
442 is_verified: false,
443 notification_count: 0,
444 follower_count: 0,
445 following_count: 0,
446 last_seen: created,
447 totp: String::new(),
448 recovery_codes: Vec::new(),
449 post_count: 0,
450 request_count: 0,
451 connections: HashMap::new(),
452 stripe_id: String::new(),
453 grants: Vec::new(),
454 associated: Vec::new(),
455 invite_code: 0,
456 secondary_permissions: SecondaryPermission::DEFAULT,
457 achievements: Vec::new(),
458 awaiting_purchase: false,
459 was_purchased: false,
460 ban_reason: String::new(),
461 is_deactivated: false,
462 ban_expire: 0,
463 checkouts: Vec::new(),
464 last_policy_consent: created,
465 close_friends_stack: 0,
466 missed_messages_count: 0,
467 views: 0,
468 shrimpcamp_link: 0,
469 }
470 }
471
472 pub fn deleted() -> Self {
474 Self {
475 username: "<deleted>".to_string(),
476 id: 0,
477 ..Default::default()
478 }
479 }
480
481 pub fn banned() -> Self {
483 Self {
484 username: "<banned>".to_string(),
485 id: 0,
486 ..Default::default()
487 }
488 }
489
490 pub fn anonymous() -> Self {
492 Self {
493 username: "anonymous".to_string(),
494 id: 0,
495 ..Default::default()
496 }
497 }
498
499 pub fn create_token(ip: &str) -> (String, Token) {
504 let unhashed = tetratto_shared::hash::uuid();
505 (
506 unhashed.clone(),
507 (
508 ip.to_string(),
509 tetratto_shared::hash::hash(unhashed),
510 unix_epoch_timestamp(),
511 ),
512 )
513 }
514
515 pub fn check_password(&self, against: String) -> bool {
517 self.password == hash_salted(against, self.salt.clone())
518 }
519
520 pub fn parse_mentions(input: &str) -> Vec<String> {
522 let mut escape: bool = false;
524 let mut at: bool = false;
525 let mut buffer: String = String::new();
526 let mut out = Vec::new();
527
528 for char in input.chars() {
530 if ((char == '\\') | (char == '/')) && !escape {
531 escape = true;
532 continue;
533 }
534
535 if (char == '@') && !escape {
536 at = true;
537 continue; }
539
540 if at {
541 if char == ' ' {
542 at = false;
544
545 if !out.contains(&buffer) {
546 out.push(buffer);
547 }
548
549 buffer = String::new();
550 continue;
551 }
552
553 buffer.push(char);
555 }
556
557 escape = false;
558 }
559
560 if !buffer.is_empty() {
561 out.push(buffer);
562 }
563
564 if out.len() > 5 {
565 return Vec::new();
567 }
568
569 out
571 }
572
573 pub fn totp(&self, issuer: Option<String>) -> Option<TOTP> {
575 if self.totp.is_empty() {
576 return None;
577 }
578
579 TOTP::new(
580 totp_rs::Algorithm::SHA1,
581 6,
582 1,
583 30,
584 self.totp.as_bytes().to_owned(),
585 Some(issuer.unwrap_or("tetratto!".to_string())),
586 self.username.clone(),
587 )
588 .ok()
589 }
590
591 pub fn clean(&mut self) {
593 self.password = String::new();
594 self.salt = String::new();
595
596 self.tokens = Vec::new();
597 self.grants = Vec::new();
598
599 self.recovery_codes = Vec::new();
600 self.totp = String::new();
601
602 self.settings = UserSettings::default();
603 self.stripe_id = String::new();
604 self.connections = HashMap::new();
605 }
606
607 pub fn get_grant_by_app_id(&self, id: usize) -> Option<&AuthGrant> {
612 self.grants.iter().find(|x| x.app == id)
613 }
614}
615
616#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
617pub enum ConnectionService {
618 Spotify,
620 LastFm,
622}
623
624#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
625pub enum ConnectionType {
626 Token,
628 PKCE,
630 None,
632}
633
634#[derive(Clone, Debug, Serialize, Deserialize)]
635pub struct ExternalConnectionInfo {
636 pub con_type: ConnectionType,
637 pub data: HashMap<String, String>,
638 pub show_on_profile: bool,
639}
640
641#[derive(Clone, Debug, Serialize, Deserialize, Default)]
642pub struct ExternalConnectionData {
643 pub external_urls: HashMap<String, String>,
644 pub data: HashMap<String, String>,
645}
646
647pub const ACHIEVEMENTS: usize = 34;
649pub const SELF_SERVE_ACHIEVEMENTS: &[AchievementName] = &[
651 AchievementName::OpenReference,
652 AchievementName::OpenTos,
653 AchievementName::OpenPrivacyPolicy,
654 AchievementName::AcceptProfileWarning,
655 AchievementName::OpenSessionSettings,
656];
657
658#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
659pub enum AchievementName {
660 CreatePost,
661 FollowUser,
662 Create50Posts,
663 Create100Posts,
664 Create1000Posts,
665 CreateQuestion,
666 EditSettings,
667 FollowedByStaff,
668 CreateDrawing,
669 OpenAchievements,
670 Get1Like,
671 Get10Likes,
672 Get50Likes,
673 Get100Likes,
674 Get25Dislikes,
675 Get1Follower,
676 Get10Followers,
677 Get50Followers,
678 Get100Followers,
679 Follow10Users,
680 JoinCommunity,
681 CreateDraft,
682 EditPost,
683 Enable2fa,
684 EditNote,
685 CreatePostWithTitle,
686 CreateRepost,
687 OpenTos,
688 OpenPrivacyPolicy,
689 OpenReference,
690 GetAllOtherAchievements,
691 AcceptProfileWarning,
692 OpenSessionSettings,
693 #[serde(other)]
694 Removed,
695}
696
697#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
698pub enum AchievementRarity {
699 Common,
700 Uncommon,
701 Rare,
702}
703
704impl AchievementName {
705 pub fn title(&self) -> &str {
706 match self {
707 Self::CreatePost => "Dear friends,",
708 Self::FollowUser => "Virtual connections...",
709 Self::Create50Posts => "Hello, world!",
710 Self::Create100Posts => "It's my world",
711 Self::Create1000Posts => "Timeline domination",
712 Self::CreateQuestion => "Big questions...",
713 Self::EditSettings => "Just how I like it!",
714 Self::FollowedByStaff => "Big Shrimpin'",
715 Self::CreateDrawing => "Modern art",
716 Self::OpenAchievements => "Welcome!",
717 Self::Get1Like => "Baby steps!",
718 Self::Get10Likes => "WOW! 10 LIKES!",
719 Self::Get50Likes => "banger post follow for more",
720 Self::Get100Likes => "everyone liked that",
721 Self::Get25Dislikes => "Sorry...",
722 Self::Get1Follower => "Friends?",
723 Self::Get10Followers => "Friends!",
724 Self::Get50Followers => "50 WHOLE FOLLOWERS??",
725 Self::Get100Followers => "Everyone is my friend!",
726 Self::Follow10Users => "Big fan",
727 Self::JoinCommunity => "A sense of community...",
728 Self::CreateDraft => "Maybe later!",
729 Self::EditPost => "Grammar police?",
730 Self::Enable2fa => "Locked in",
731 Self::EditNote => "I take it back!",
732 Self::CreatePostWithTitle => "Must declutter",
733 Self::CreateRepost => "More than a like or comment...",
734 Self::OpenTos => "Well informed!",
735 Self::OpenPrivacyPolicy => "Privacy conscious",
736 Self::OpenReference => "What does this do?",
737 Self::GetAllOtherAchievements => "The final performance",
738 Self::AcceptProfileWarning => "I accept the risks!",
739 Self::OpenSessionSettings => "Am I alone in here?",
740 Self::Removed => "Removed achievement",
741 }
742 }
743
744 pub fn description(&self) -> &str {
745 match self {
746 Self::CreatePost => "Create your first post!",
747 Self::FollowUser => "Follow somebody!",
748 Self::Create50Posts => "Create your 50th post.",
749 Self::Create100Posts => "Create your 100th post.",
750 Self::Create1000Posts => "Create your 1000th post.",
751 Self::CreateQuestion => "Ask your first question!",
752 Self::EditSettings => "Edit your settings.",
753 Self::FollowedByStaff => "Get followed by a staff member!",
754 Self::CreateDrawing => "Include a drawing in a question.",
755 Self::OpenAchievements => "Open the achievements page.",
756 Self::Get1Like => "Get 1 like on a post! Good job!",
757 Self::Get10Likes => "Get 10 likes on one post.",
758 Self::Get50Likes => "Get 50 likes on one post.",
759 Self::Get100Likes => "Get 100 likes on one post.",
760 Self::Get25Dislikes => "Get 25 dislikes on one post... :(",
761 Self::Get1Follower => "Get 1 follower. Cool!",
762 Self::Get10Followers => "Get 10 followers. You're getting popular!",
763 Self::Get50Followers => "Get 50 followers. Okay, you're fairly popular!",
764 Self::Get100Followers => "Get 100 followers. You might be famous..?",
765 Self::Follow10Users => "Follow 10 other users. I'm sure people appreciate it!",
766 Self::JoinCommunity => "Join a community. Welcome!",
767 Self::CreateDraft => "Save a post as a draft.",
768 Self::EditPost => "Edit a post.",
769 Self::Enable2fa => "Enable TOTP 2FA.",
770 Self::EditNote => "Edit a note.",
771 Self::CreatePostWithTitle => "Create a post with a title.",
772 Self::CreateRepost => "Create a repost or quote.",
773 Self::OpenTos => "Open the terms of service.",
774 Self::OpenPrivacyPolicy => "Open the privacy policy.",
775 Self::OpenReference => "Open the source code reference documentation.",
776 Self::GetAllOtherAchievements => "Get every other achievement.",
777 Self::AcceptProfileWarning => "Accept a profile warning.",
778 Self::OpenSessionSettings => "Open your session settings.",
779 Self::Removed => "An achievement that was removed.",
780 }
781 }
782
783 pub fn rarity(&self) -> AchievementRarity {
784 use AchievementRarity::*;
786 match self {
787 Self::CreatePost => Common,
788 Self::FollowUser => Common,
789 Self::Create50Posts => Uncommon,
790 Self::Create100Posts => Uncommon,
791 Self::Create1000Posts => Rare,
792 Self::CreateQuestion => Common,
793 Self::EditSettings => Common,
794 Self::FollowedByStaff => Rare,
795 Self::CreateDrawing => Common,
796 Self::OpenAchievements => Common,
797 Self::Get1Like => Common,
798 Self::Get10Likes => Common,
799 Self::Get50Likes => Uncommon,
800 Self::Get100Likes => Rare,
801 Self::Get25Dislikes => Uncommon,
802 Self::Get1Follower => Common,
803 Self::Get10Followers => Common,
804 Self::Get50Followers => Uncommon,
805 Self::Get100Followers => Rare,
806 Self::Follow10Users => Common,
807 Self::JoinCommunity => Common,
808 Self::CreateDraft => Common,
809 Self::EditPost => Common,
810 Self::Enable2fa => Rare,
811 Self::EditNote => Uncommon,
812 Self::CreatePostWithTitle => Common,
813 Self::CreateRepost => Common,
814 Self::OpenTos => Uncommon,
815 Self::OpenPrivacyPolicy => Uncommon,
816 Self::OpenReference => Uncommon,
817 Self::GetAllOtherAchievements => Rare,
818 Self::AcceptProfileWarning => Common,
819 Self::OpenSessionSettings => Common,
820 Self::Removed => Rare,
821 }
822 }
823}
824
825impl From<AchievementName> for Achievement {
826 fn from(val: AchievementName) -> Self {
827 Achievement {
828 name: val,
829 unlocked: unix_epoch_timestamp(),
830 }
831 }
832}
833
834#[derive(Clone, Debug, Serialize, Deserialize)]
835pub struct Achievement {
836 pub name: AchievementName,
837 pub unlocked: usize,
838}
839
840#[derive(Debug, Serialize, Deserialize)]
841pub struct Notification {
842 pub id: usize,
843 pub created: usize,
844 pub title: String,
845 pub content: String,
846 pub owner: usize,
847 pub read: bool,
848 pub tag: String,
849}
850
851impl Notification {
852 pub fn new(title: String, content: String, owner: usize) -> Self {
854 Self {
855 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
856 created: unix_epoch_timestamp(),
857 title,
858 content,
859 owner,
860 read: false,
861 tag: String::new(),
862 }
863 }
864}
865
866#[derive(Clone, Debug, Serialize, Deserialize)]
867pub struct UserFollow {
868 pub id: usize,
869 pub created: usize,
870 pub initiator: usize,
871 pub receiver: usize,
872}
873
874impl UserFollow {
875 pub fn new(initiator: usize, receiver: usize) -> Self {
877 Self {
878 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
879 created: unix_epoch_timestamp(),
880 initiator,
881 receiver,
882 }
883 }
884}
885
886#[derive(Serialize, Deserialize, PartialEq, Eq)]
887pub enum FollowResult {
888 Requested,
890 Followed,
892}
893
894#[derive(Serialize, Deserialize)]
895pub struct UserBlock {
896 pub id: usize,
897 pub created: usize,
898 pub initiator: usize,
899 pub receiver: usize,
900}
901
902impl UserBlock {
903 pub fn new(initiator: usize, receiver: usize) -> Self {
905 Self {
906 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
907 created: unix_epoch_timestamp(),
908 initiator,
909 receiver,
910 }
911 }
912}
913
914#[derive(Serialize, Deserialize)]
915pub struct IpBlock {
916 pub id: usize,
917 pub created: usize,
918 pub initiator: usize,
919 pub receiver: String,
920}
921
922impl IpBlock {
923 pub fn new(initiator: usize, receiver: String) -> Self {
925 Self {
926 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
927 created: unix_epoch_timestamp(),
928 initiator,
929 receiver,
930 }
931 }
932}
933
934#[derive(Serialize, Deserialize)]
935pub struct IpBan {
936 pub ip: String,
937 pub created: usize,
938 pub reason: String,
939 pub moderator: usize,
940}
941
942impl IpBan {
943 pub fn new(ip: String, moderator: usize, reason: String) -> Self {
945 Self {
946 ip,
947 created: unix_epoch_timestamp(),
948 reason,
949 moderator,
950 }
951 }
952}
953
954#[derive(Serialize, Deserialize)]
955pub struct UserWarning {
956 pub id: usize,
957 pub created: usize,
958 pub receiver: usize,
959 pub moderator: usize,
960 pub content: String,
961}
962
963impl UserWarning {
964 pub fn new(user: usize, moderator: usize, content: String) -> Self {
966 Self {
967 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
968 created: unix_epoch_timestamp(),
969 receiver: user,
970 moderator,
971 content,
972 }
973 }
974}
975
976#[derive(Clone, Debug, Serialize, Deserialize)]
977pub struct InviteCode {
978 pub id: usize,
979 pub created: usize,
980 pub owner: usize,
981 pub code: String,
982 pub is_used: bool,
983}
984
985impl InviteCode {
986 pub fn new(owner: usize) -> Self {
988 Self {
989 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
990 created: unix_epoch_timestamp(),
991 owner,
992 code: salt(),
993 is_used: false,
994 }
995 }
996}
997
998#[derive(Clone, Debug, Serialize, Deserialize)]
999pub struct ProfileView {
1000 pub id: usize,
1001 pub created: usize,
1002 pub owner: usize,
1003 pub profile: usize,
1004}
1005
1006impl ProfileView {
1007 pub fn new(owner: usize, profile: usize) -> Self {
1009 Self {
1010 id: Snowflake::new().to_string().parse::<usize>().unwrap(),
1011 created: unix_epoch_timestamp(),
1012 owner,
1013 profile,
1014 }
1015 }
1016}