Skip to main content

yggdrasil/models/
user.rs

1//! 用户模型。
2//!
3//! 定义用户角色、内部用户结构体以及可暴露给前端的 PublicUser。
4//! User 包含密码哈希等敏感字段,PublicUser 用于在 API 中隐藏这些字段。
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9/// 用户角色枚举。
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub enum UserRole {
12    /// 管理员,拥有全部后台权限。
13    Admin,
14    /// 被禁用的用户,无法登录或操作。
15    Blocked,
16}
17
18impl UserRole {
19    /// 将数据库中的角色字符串解析为 UserRole,无法识别时返回 None。
20    #[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/// 会话缓存使用的轻量用户结构体,不含密码哈希。
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct SessionUser {
33    /// 用户主键。
34    pub id: i32,
35    /// 用户名。
36    pub username: String,
37    /// 邮箱地址。
38    pub email: String,
39    /// 用户角色。
40    pub role: UserRole,
41    /// 账户创建时间。
42    pub created_at: DateTime<Utc>,
43    /// 会话世代号,签发 session 时记录;与 users 表当前值不一致则 session 失效。
44    pub session_generation: i32,
45}
46
47/// 可公开的用户信息,从 User 转换而来,不含密码哈希。
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct PublicUser {
50    /// 用户主键。
51    pub id: i32,
52    /// 用户名。
53    pub username: String,
54    /// 邮箱地址。
55    pub email: String,
56    /// 用户角色。
57    pub role: UserRole,
58    /// 账户创建时间。
59    pub created_at: DateTime<Utc>,
60}
61
62impl From<SessionUser> for PublicUser {
63    /// 将 SessionUser 转换为 PublicUser。
64    fn from(u: SessionUser) -> Self {
65        PublicUser {
66            id: u.id,
67            username: u.username,
68            email: u.email,
69            role: u.role,
70            created_at: u.created_at,
71        }
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use chrono::{TimeZone, Utc};
79
80    fn sample_user() -> SessionUser {
81        SessionUser {
82            id: 1,
83            username: "admin".to_string(),
84            email: "admin@test.com".to_string(),
85            role: UserRole::Admin,
86            created_at: Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
87            session_generation: 0,
88        }
89    }
90
91    #[test]
92    #[cfg(feature = "server")]
93    fn user_role_from_str() {
94        assert_eq!(UserRole::from_str("admin"), Some(UserRole::Admin));
95        assert_eq!(UserRole::from_str("blocked"), Some(UserRole::Blocked));
96        assert_eq!(UserRole::from_str("unknown"), None);
97        assert_eq!(UserRole::from_str(""), None);
98    }
99
100    #[test]
101    fn user_role_serde_roundtrip() {
102        let json = serde_json::to_string(&UserRole::Admin).unwrap();
103        assert_eq!(
104            serde_json::from_str::<UserRole>(&json).unwrap(),
105            UserRole::Admin
106        );
107    }
108
109    #[test]
110    fn session_user_to_public_user_conversion() {
111        // D2:User 结构体已删除(生产路径 auth.rs 直接从 row 构造 SessionUser),
112        // 仅保留 From<SessionUser> for PublicUser(auth.rs:431 在用)。
113        let session = sample_user();
114        let public: PublicUser = session.clone().into();
115        assert_eq!(public.id, session.id);
116        assert_eq!(public.username, session.username);
117        assert_eq!(public.email, session.email);
118        assert_eq!(public.role, session.role);
119        assert_eq!(public.created_at, session.created_at);
120    }
121
122    #[test]
123    fn public_user_excludes_session_generation() {
124        // PublicUser 不含 session_generation(敏感会话字段)。
125        let session = sample_user();
126        let public: PublicUser = session.into();
127        let json = serde_json::to_string(&public).unwrap();
128        assert!(!json.contains("session_generation"));
129    }
130}