systemprompt_users/models/
mod.rs1use 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}