Skip to main content

systemprompt_users/models/
mod.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use systemprompt_identifiers::{ApiKeyId, DeviceCertId, SessionId, UserId};
5
6pub use systemprompt_models::auth::{UserRole, UserStatus};
7
8#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
9pub struct User {
10    #[sqlx(try_from = "String")]
11    pub id: UserId,
12    pub name: String,
13    pub email: String,
14    pub full_name: Option<String>,
15    pub display_name: Option<String>,
16    pub status: Option<String>,
17    pub email_verified: Option<bool>,
18    pub roles: Vec<String>,
19    pub avatar_url: Option<String>,
20    pub is_bot: bool,
21    pub is_scanner: bool,
22    pub created_at: Option<DateTime<Utc>>,
23    pub updated_at: Option<DateTime<Utc>>,
24}
25
26impl User {
27    pub fn is_active(&self) -> bool {
28        self.status.as_deref() == Some(UserStatus::Active.as_str())
29    }
30
31    pub fn is_admin(&self) -> bool {
32        self.roles.contains(&UserRole::Admin.as_str().to_string())
33    }
34
35    pub fn has_role(&self, role: UserRole) -> bool {
36        self.roles.contains(&role.as_str().to_string())
37    }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
41pub struct UserActivity {
42    #[sqlx(try_from = "String")]
43    pub user_id: UserId,
44    pub last_active: Option<DateTime<Utc>>,
45    pub session_count: i64,
46    pub task_count: i64,
47    pub message_count: i64,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
51pub struct UserWithSessions {
52    #[sqlx(try_from = "String")]
53    pub id: UserId,
54    pub name: String,
55    pub email: String,
56    pub full_name: Option<String>,
57    pub status: Option<String>,
58    pub roles: Vec<String>,
59    pub created_at: Option<DateTime<Utc>>,
60    pub active_sessions: i64,
61    pub last_session_at: Option<DateTime<Utc>>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct UserSession {
66    pub session_id: SessionId,
67    pub user_id: Option<UserId>,
68    pub ip_address: Option<String>,
69    pub user_agent: Option<String>,
70    pub device_type: Option<String>,
71    pub started_at: Option<DateTime<Utc>>,
72    pub last_activity_at: Option<DateTime<Utc>>,
73    pub ended_at: Option<DateTime<Utc>>,
74}
75
76#[derive(Debug, Clone, FromRow)]
77pub struct UserSessionRow {
78    #[sqlx(try_from = "String")]
79    pub session_id: SessionId,
80    pub user_id: Option<UserId>,
81    pub ip_address: Option<String>,
82    pub user_agent: Option<String>,
83    pub device_type: Option<String>,
84    pub started_at: Option<DateTime<Utc>>,
85    pub last_activity_at: Option<DateTime<Utc>>,
86    pub ended_at: Option<DateTime<Utc>>,
87}
88
89impl From<UserSessionRow> for UserSession {
90    fn from(row: UserSessionRow) -> Self {
91        Self {
92            session_id: row.session_id,
93            user_id: row.user_id,
94            ip_address: row.ip_address,
95            user_agent: row.user_agent,
96            device_type: row.device_type,
97            started_at: row.started_at,
98            last_activity_at: row.last_activity_at,
99            ended_at: row.ended_at,
100        }
101    }
102}
103
104#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
105pub struct UserStats {
106    pub total: i64,
107    pub created_24h: i64,
108    pub created_7d: i64,
109    pub created_30d: i64,
110    pub active: i64,
111    pub suspended: i64,
112    pub admins: i64,
113    pub anonymous: i64,
114    pub bots: i64,
115    pub oldest_user: Option<DateTime<Utc>>,
116    pub newest_user: Option<DateTime<Utc>>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct UserCountBreakdown {
121    pub total: i64,
122    pub by_status: std::collections::HashMap<String, i64>,
123    pub by_role: std::collections::HashMap<String, i64>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct UserExport {
128    pub id: UserId,
129    pub name: String,
130    pub email: String,
131    pub full_name: Option<String>,
132    pub display_name: Option<String>,
133    pub status: Option<String>,
134    pub email_verified: Option<bool>,
135    pub roles: Vec<String>,
136    pub is_bot: bool,
137    pub is_scanner: bool,
138    pub created_at: Option<DateTime<Utc>>,
139    pub updated_at: Option<DateTime<Utc>>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
143pub struct UserApiKey {
144    #[sqlx(try_from = "String")]
145    pub id: ApiKeyId,
146    #[sqlx(try_from = "String")]
147    pub user_id: UserId,
148    pub name: String,
149    pub key_prefix: String,
150    pub key_hash: String,
151    pub created_at: Option<DateTime<Utc>>,
152    pub last_used_at: Option<DateTime<Utc>>,
153    pub expires_at: Option<DateTime<Utc>>,
154    pub revoked_at: Option<DateTime<Utc>>,
155}
156
157impl UserApiKey {
158    pub fn is_active(&self, now: DateTime<Utc>) -> bool {
159        if self.revoked_at.is_some() {
160            return false;
161        }
162        if let Some(expires_at) = self.expires_at {
163            if now >= expires_at {
164                return false;
165            }
166        }
167        true
168    }
169}
170
171#[derive(Debug, Clone)]
172pub struct NewApiKey {
173    pub record: UserApiKey,
174    pub secret: String,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
178pub struct UserDeviceCert {
179    #[sqlx(try_from = "String")]
180    pub id: DeviceCertId,
181    #[sqlx(try_from = "String")]
182    pub user_id: UserId,
183    pub fingerprint: String,
184    pub label: String,
185    pub enrolled_at: Option<DateTime<Utc>>,
186    pub revoked_at: Option<DateTime<Utc>>,
187}
188
189impl UserDeviceCert {
190    pub const fn is_active(&self) -> bool {
191        self.revoked_at.is_none()
192    }
193}
194
195impl From<User> for UserExport {
196    fn from(user: User) -> Self {
197        Self {
198            id: user.id,
199            name: user.name,
200            email: user.email,
201            full_name: user.full_name,
202            display_name: user.display_name,
203            status: user.status,
204            email_verified: user.email_verified,
205            roles: user.roles,
206            is_bot: user.is_bot,
207            is_scanner: user.is_scanner,
208            created_at: user.created_at,
209            updated_at: user.updated_at,
210        }
211    }
212}