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