systemprompt_users/repository/user/
operations.rs1use chrono::Utc;
7use systemprompt_identifiers::UserId;
8
9use crate::error::Result;
10use crate::models::{User, UserRole, UserStatus, normalise_email};
11use crate::repository::UserRepository;
12
13#[derive(Debug)]
14pub struct UpdateUserParams<'a> {
15 pub email: &'a str,
16 pub full_name: Option<&'a str>,
17 pub display_name: Option<&'a str>,
18 pub status: UserStatus,
19}
20
21impl UserRepository {
22 pub async fn create(
23 &self,
24 name: &str,
25 email: &str,
26 full_name: Option<&str>,
27 display_name: Option<&str>,
28 ) -> Result<User> {
29 let now = Utc::now();
30 let id = UserId::new(uuid::Uuid::new_v4().to_string());
31 let display_name_val = display_name.or(full_name);
32 let status = UserStatus::Active.as_str();
33 let role = UserRole::User.as_str();
34 let email = normalise_email(email);
35
36 let row = sqlx::query_as!(
37 User,
38 r#"
39 INSERT INTO users (
40 id, name, email, full_name, display_name,
41 status, email_verified, roles, is_bot,
42 created_at, updated_at
43 )
44 VALUES ($1, $2, $3, $4, $5, $6, false, ARRAY[$7]::TEXT[], false, $8, $8)
45 RETURNING id, name, email, full_name, display_name, status, email_verified,
46 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
47 "#,
48 id.as_str(),
49 name,
50 email,
51 full_name,
52 display_name_val,
53 status,
54 role,
55 now
56 )
57 .fetch_one(&*self.write_pool)
58 .await?;
59
60 Ok(row)
61 }
62
63 pub async fn create_if_absent(
64 &self,
65 name: &str,
66 email: &str,
67 full_name: Option<&str>,
68 display_name: Option<&str>,
69 ) -> Result<Option<User>> {
70 let now = Utc::now();
71 let id = UserId::new(uuid::Uuid::new_v4().to_string());
72 let display_name_val = display_name.or(full_name);
73 let status = UserStatus::Active.as_str();
74 let role = UserRole::User.as_str();
75 let email = normalise_email(email);
76
77 let row = sqlx::query_as!(
78 User,
79 r#"
80 INSERT INTO users (
81 id, name, email, full_name, display_name,
82 status, email_verified, roles, is_bot,
83 created_at, updated_at
84 )
85 VALUES ($1, $2, $3, $4, $5, $6, false, ARRAY[$7]::TEXT[], false, $8, $8)
86 ON CONFLICT DO NOTHING
87 RETURNING id, name, email, full_name, display_name, status, email_verified,
88 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
89 "#,
90 id.as_str(),
91 name,
92 email,
93 full_name,
94 display_name_val,
95 status,
96 role,
97 now
98 )
99 .fetch_optional(&*self.write_pool)
100 .await?;
101
102 Ok(row)
103 }
104
105 pub async fn create_anonymous(&self, fingerprint: &str) -> Result<User> {
106 let email = normalise_email(&format!("{}@anonymous.local", fingerprint));
107
108 if let Some(existing) = sqlx::query_as!(
109 User,
110 r#"
111 SELECT id, name, email, full_name, display_name, status, email_verified,
112 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
113 FROM users
114 WHERE email = $1
115 "#,
116 email
117 )
118 .fetch_optional(&*self.pool)
119 .await?
120 {
121 return Ok(existing);
122 }
123
124 let user_id = uuid::Uuid::new_v4();
125 let id = UserId::new(user_id.to_string());
126 let name = format!("anonymous_{}", &user_id.to_string()[..8]);
127 let now = Utc::now();
128 let status = UserStatus::Active.as_str();
129 let role = UserRole::Anonymous.as_str();
130
131 let row = sqlx::query_as!(
132 User,
133 r#"
134 INSERT INTO users (
135 id, name, email, status, email_verified, roles,
136 is_bot, created_at, updated_at
137 )
138 VALUES ($1, $2, $3, $4, false, ARRAY[$5]::TEXT[], false, $6, $6)
139 ON CONFLICT (email) DO UPDATE SET updated_at = $6
140 RETURNING id, name, email, full_name, display_name, status, email_verified,
141 roles, avatar_url, is_bot, is_scanner, created_at, updated_at
142 "#,
143 id.as_str(),
144 name,
145 email,
146 status,
147 role,
148 now
149 )
150 .fetch_one(&*self.write_pool)
151 .await?;
152
153 Ok(row)
154 }
155}