1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub enum UserRole {
12 Admin,
14 Blocked,
16}
17
18impl UserRole {
19 #[cfg(feature = "server")]
21 pub fn from_str(s: &str) -> Option<Self> {
22 match s {
23 "admin" => Some(UserRole::Admin),
24 "blocked" => Some(UserRole::Blocked),
25 _ => None,
26 }
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct SessionUser {
33 pub id: i32,
35 pub username: String,
37 pub email: String,
39 pub display_name: Option<String>,
41 pub avatar_url: Option<String>,
43 pub role: UserRole,
45 pub created_at: DateTime<Utc>,
47 pub session_generation: i32,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct PublicUser {
54 pub id: i32,
56 pub username: String,
58 pub email: String,
60 pub display_name: Option<String>,
62 pub avatar_url: Option<String>,
64 pub role: UserRole,
66 pub created_at: DateTime<Utc>,
68}
69
70impl From<SessionUser> for PublicUser {
71 fn from(u: SessionUser) -> Self {
73 PublicUser {
74 id: u.id,
75 username: u.username,
76 email: u.email,
77 display_name: u.display_name,
78 avatar_url: u.avatar_url,
79 role: u.role,
80 created_at: u.created_at,
81 }
82 }
83}
84
85impl PublicUser {
86 pub fn display_label(&self) -> &str {
88 self.display_name.as_deref().unwrap_or(&self.username)
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use chrono::{TimeZone, Utc};
96
97 fn sample_user() -> SessionUser {
98 SessionUser {
99 id: 1,
100 username: "admin".to_string(),
101 email: "admin@test.com".to_string(),
102 display_name: None,
103 avatar_url: None,
104 role: UserRole::Admin,
105 created_at: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
106 session_generation: 0,
107 }
108 }
109
110 #[test]
111 #[cfg(feature = "server")]
112 fn user_role_from_str() {
113 assert_eq!(UserRole::from_str("admin"), Some(UserRole::Admin));
114 assert_eq!(UserRole::from_str("blocked"), Some(UserRole::Blocked));
115 assert_eq!(UserRole::from_str("unknown"), None);
116 assert_eq!(UserRole::from_str(""), None);
117 }
118
119 #[test]
120 fn user_role_serde_roundtrip() {
121 let json = serde_json::to_string(&UserRole::Admin).unwrap();
122 assert_eq!(
123 serde_json::from_str::<UserRole>(&json).unwrap(),
124 UserRole::Admin
125 );
126 }
127
128 #[test]
129 fn session_user_to_public_user_conversion() {
130 let session = sample_user();
133 let public: PublicUser = session.clone().into();
134 assert_eq!(public.id, session.id);
135 assert_eq!(public.username, session.username);
136 assert_eq!(public.email, session.email);
137 assert_eq!(public.role, session.role);
138 assert_eq!(public.created_at, session.created_at);
139 }
140
141 #[test]
142 fn public_user_excludes_session_generation() {
143 let session = sample_user();
145 let public: PublicUser = session.into();
146 let json = serde_json::to_string(&public).unwrap();
147 assert!(!json.contains("session_generation"));
148 }
149
150 #[test]
151 fn display_label_falls_back_to_username() {
152 let session = sample_user();
153 let mut public: PublicUser = session.into();
154 assert_eq!(public.display_label(), "admin");
156 public.display_name = Some("站长".to_string());
158 assert_eq!(public.display_label(), "站长");
159 }
160}