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