users.rs

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