1use super::*;
2
3impl Database {
4 /// Creates a new user.
5 pub async fn create_user(
6 &self,
7 email_address: &str,
8 admin: bool,
9 params: NewUserParams,
10 ) -> Result<NewUserResult> {
11 self.transaction(|tx| async {
12 let tx = tx;
13 let user = user::Entity::insert(user::ActiveModel {
14 email_address: ActiveValue::set(Some(email_address.into())),
15 github_login: ActiveValue::set(params.github_login.clone()),
16 github_user_id: ActiveValue::set(Some(params.github_user_id)),
17 admin: ActiveValue::set(admin),
18 metrics_id: ActiveValue::set(Uuid::new_v4()),
19 ..Default::default()
20 })
21 .on_conflict(
22 OnConflict::column(user::Column::GithubLogin)
23 .update_columns([
24 user::Column::Admin,
25 user::Column::EmailAddress,
26 user::Column::GithubUserId,
27 ])
28 .to_owned(),
29 )
30 .exec_with_returning(&*tx)
31 .await?;
32
33 Ok(NewUserResult {
34 user_id: user.id,
35 metrics_id: user.metrics_id.to_string(),
36 signup_device_id: None,
37 inviting_user_id: None,
38 })
39 })
40 .await
41 }
42
43 /// Returns a user by ID. There are no access checks here, so this should only be used internally.
44 pub async fn get_user_by_id(&self, id: UserId) -> Result<Option<user::Model>> {
45 self.transaction(|tx| async move { Ok(user::Entity::find_by_id(id).one(&*tx).await?) })
46 .await
47 }
48
49 /// Returns all users by ID. There are no access checks here, so this should only be used internally.
50 pub async fn get_users_by_ids(&self, ids: Vec<UserId>) -> Result<Vec<user::Model>> {
51 self.transaction(|tx| async {
52 let tx = tx;
53 Ok(user::Entity::find()
54 .filter(user::Column::Id.is_in(ids.iter().copied()))
55 .all(&*tx)
56 .await?)
57 })
58 .await
59 }
60
61 /// Returns a user by GitHub login. There are no access checks here, so this should only be used internally.
62 pub async fn get_user_by_github_login(&self, github_login: &str) -> Result<Option<User>> {
63 self.transaction(|tx| async move {
64 Ok(user::Entity::find()
65 .filter(user::Column::GithubLogin.eq(github_login))
66 .one(&*tx)
67 .await?)
68 })
69 .await
70 }
71
72 pub async fn get_or_create_user_by_github_account(
73 &self,
74 github_login: &str,
75 github_user_id: Option<i32>,
76 github_email: Option<&str>,
77 initial_channel_id: Option<ChannelId>,
78 ) -> Result<User> {
79 self.transaction(|tx| async move {
80 self.get_or_create_user_by_github_account_tx(
81 github_login,
82 github_user_id,
83 github_email,
84 initial_channel_id,
85 &tx,
86 )
87 .await
88 })
89 .await
90 }
91
92 pub async fn get_or_create_user_by_github_account_tx(
93 &self,
94 github_login: &str,
95 github_user_id: Option<i32>,
96 github_email: Option<&str>,
97 initial_channel_id: Option<ChannelId>,
98 tx: &DatabaseTransaction,
99 ) -> Result<User> {
100 if let Some(github_user_id) = github_user_id {
101 if let Some(user_by_github_user_id) = user::Entity::find()
102 .filter(user::Column::GithubUserId.eq(github_user_id))
103 .one(tx)
104 .await?
105 {
106 let mut user_by_github_user_id = user_by_github_user_id.into_active_model();
107 user_by_github_user_id.github_login = ActiveValue::set(github_login.into());
108 Ok(user_by_github_user_id.update(tx).await?)
109 } else if let Some(user_by_github_login) = user::Entity::find()
110 .filter(user::Column::GithubLogin.eq(github_login))
111 .one(tx)
112 .await?
113 {
114 let mut user_by_github_login = user_by_github_login.into_active_model();
115 user_by_github_login.github_user_id = ActiveValue::set(Some(github_user_id));
116 Ok(user_by_github_login.update(tx).await?)
117 } else {
118 let user = user::Entity::insert(user::ActiveModel {
119 email_address: ActiveValue::set(github_email.map(|email| email.into())),
120 github_login: ActiveValue::set(github_login.into()),
121 github_user_id: ActiveValue::set(Some(github_user_id)),
122 admin: ActiveValue::set(false),
123 invite_count: ActiveValue::set(0),
124 invite_code: ActiveValue::set(None),
125 metrics_id: ActiveValue::set(Uuid::new_v4()),
126 ..Default::default()
127 })
128 .exec_with_returning(tx)
129 .await?;
130 if let Some(channel_id) = initial_channel_id {
131 channel_member::Entity::insert(channel_member::ActiveModel {
132 id: ActiveValue::NotSet,
133 channel_id: ActiveValue::Set(channel_id),
134 user_id: ActiveValue::Set(user.id),
135 accepted: ActiveValue::Set(true),
136 role: ActiveValue::Set(ChannelRole::Guest),
137 })
138 .exec(tx)
139 .await?;
140 }
141 Ok(user)
142 }
143 } else {
144 let user = user::Entity::find()
145 .filter(user::Column::GithubLogin.eq(github_login))
146 .one(tx)
147 .await?
148 .ok_or_else(|| anyhow!("no such user {}", github_login))?;
149 Ok(user)
150 }
151 }
152
153 /// get_all_users returns the next page of users. To get more call again with
154 /// the same limit and the page incremented by 1.
155 pub async fn get_all_users(&self, page: u32, limit: u32) -> Result<Vec<User>> {
156 self.transaction(|tx| async move {
157 Ok(user::Entity::find()
158 .order_by_asc(user::Column::GithubLogin)
159 .limit(limit as u64)
160 .offset(page as u64 * limit as u64)
161 .all(&*tx)
162 .await?)
163 })
164 .await
165 }
166
167 /// Returns the metrics id for the user.
168 pub async fn get_user_metrics_id(&self, id: UserId) -> Result<String> {
169 #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
170 enum QueryAs {
171 MetricsId,
172 }
173
174 self.transaction(|tx| async move {
175 let metrics_id: Uuid = user::Entity::find_by_id(id)
176 .select_only()
177 .column(user::Column::MetricsId)
178 .into_values::<_, QueryAs>()
179 .one(&*tx)
180 .await?
181 .ok_or_else(|| anyhow!("could not find user"))?;
182 Ok(metrics_id.to_string())
183 })
184 .await
185 }
186
187 /// Sets "connected_once" on the user for analytics.
188 pub async fn set_user_connected_once(&self, id: UserId, connected_once: bool) -> Result<()> {
189 self.transaction(|tx| async move {
190 user::Entity::update_many()
191 .filter(user::Column::Id.eq(id))
192 .set(user::ActiveModel {
193 connected_once: ActiveValue::set(connected_once),
194 ..Default::default()
195 })
196 .exec(&*tx)
197 .await?;
198 Ok(())
199 })
200 .await
201 }
202
203 /// hard delete the user.
204 pub async fn destroy_user(&self, id: UserId) -> Result<()> {
205 self.transaction(|tx| async move {
206 access_token::Entity::delete_many()
207 .filter(access_token::Column::UserId.eq(id))
208 .exec(&*tx)
209 .await?;
210 user::Entity::delete_by_id(id).exec(&*tx).await?;
211 Ok(())
212 })
213 .await
214 }
215
216 /// Find users where github_login ILIKE name_query.
217 pub async fn fuzzy_search_users(&self, name_query: &str, limit: u32) -> Result<Vec<User>> {
218 self.transaction(|tx| async {
219 let tx = tx;
220 let like_string = Self::fuzzy_like_string(name_query);
221 let query = "
222 SELECT users.*
223 FROM users
224 WHERE github_login ILIKE $1
225 ORDER BY github_login <-> $2
226 LIMIT $3
227 ";
228
229 Ok(user::Entity::find()
230 .from_raw_sql(Statement::from_sql_and_values(
231 self.pool.get_database_backend(),
232 query,
233 vec![like_string.into(), name_query.into(), limit.into()],
234 ))
235 .all(&*tx)
236 .await?)
237 })
238 .await
239 }
240
241 /// fuzzy_like_string creates a string for matching in-order using fuzzy_search_users.
242 /// e.g. "cir" would become "%c%i%r%"
243 pub fn fuzzy_like_string(string: &str) -> String {
244 let mut result = String::with_capacity(string.len() * 2 + 1);
245 for c in string.chars() {
246 if c.is_alphanumeric() {
247 result.push('%');
248 result.push(c);
249 }
250 }
251 result.push('%');
252 result
253 }
254
255 /// Creates a new feature flag.
256 pub async fn create_user_flag(&self, flag: &str) -> Result<FlagId> {
257 self.transaction(|tx| async move {
258 let flag = feature_flag::Entity::insert(feature_flag::ActiveModel {
259 flag: ActiveValue::set(flag.to_string()),
260 ..Default::default()
261 })
262 .exec(&*tx)
263 .await?
264 .last_insert_id;
265
266 Ok(flag)
267 })
268 .await
269 }
270
271 /// Add the given user to the feature flag
272 pub async fn add_user_flag(&self, user: UserId, flag: FlagId) -> Result<()> {
273 self.transaction(|tx| async move {
274 user_feature::Entity::insert(user_feature::ActiveModel {
275 user_id: ActiveValue::set(user),
276 feature_id: ActiveValue::set(flag),
277 })
278 .exec(&*tx)
279 .await?;
280
281 Ok(())
282 })
283 .await
284 }
285
286 /// Returns the active flags for the user.
287 pub async fn get_user_flags(&self, user: UserId) -> Result<Vec<String>> {
288 self.transaction(|tx| async move {
289 #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
290 enum QueryAs {
291 Flag,
292 }
293
294 let flags = user::Model {
295 id: user,
296 ..Default::default()
297 }
298 .find_linked(user::UserFlags)
299 .select_only()
300 .column(feature_flag::Column::Flag)
301 .into_values::<_, QueryAs>()
302 .all(&*tx)
303 .await?;
304
305 Ok(flags)
306 })
307 .await
308 }
309}