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_user_metrics_id(&self, id: UserId) -> Result<String> {
127        #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
128        enum QueryAs {
129            MetricsId,
130        }
131
132        self.transaction(|tx| async move {
133            let metrics_id: Uuid = user::Entity::find_by_id(id)
134                .select_only()
135                .column(user::Column::MetricsId)
136                .into_values::<_, QueryAs>()
137                .one(&*tx)
138                .await?
139                .ok_or_else(|| anyhow!("could not find user"))?;
140            Ok(metrics_id.to_string())
141        })
142        .await
143    }
144
145    pub async fn set_user_connected_once(&self, id: UserId, connected_once: bool) -> Result<()> {
146        self.transaction(|tx| async move {
147            user::Entity::update_many()
148                .filter(user::Column::Id.eq(id))
149                .set(user::ActiveModel {
150                    connected_once: ActiveValue::set(connected_once),
151                    ..Default::default()
152                })
153                .exec(&*tx)
154                .await?;
155            Ok(())
156        })
157        .await
158    }
159
160    pub async fn destroy_user(&self, id: UserId) -> Result<()> {
161        self.transaction(|tx| async move {
162            access_token::Entity::delete_many()
163                .filter(access_token::Column::UserId.eq(id))
164                .exec(&*tx)
165                .await?;
166            user::Entity::delete_by_id(id).exec(&*tx).await?;
167            Ok(())
168        })
169        .await
170    }
171
172    pub async fn fuzzy_search_users(&self, name_query: &str, limit: u32) -> Result<Vec<User>> {
173        self.transaction(|tx| async {
174            let tx = tx;
175            let like_string = Self::fuzzy_like_string(name_query);
176            let query = "
177                SELECT users.*
178                FROM users
179                WHERE github_login ILIKE $1
180                ORDER BY github_login <-> $2
181                LIMIT $3
182            ";
183
184            Ok(user::Entity::find()
185                .from_raw_sql(Statement::from_sql_and_values(
186                    self.pool.get_database_backend(),
187                    query,
188                    vec![like_string.into(), name_query.into(), limit.into()],
189                ))
190                .all(&*tx)
191                .await?)
192        })
193        .await
194    }
195
196    pub fn fuzzy_like_string(string: &str) -> String {
197        let mut result = String::with_capacity(string.len() * 2 + 1);
198        for c in string.chars() {
199            if c.is_alphanumeric() {
200                result.push('%');
201                result.push(c);
202            }
203        }
204        result.push('%');
205        result
206    }
207
208    pub async fn create_user_flag(&self, flag: &str) -> Result<FlagId> {
209        self.transaction(|tx| async move {
210            let flag = feature_flag::Entity::insert(feature_flag::ActiveModel {
211                flag: ActiveValue::set(flag.to_string()),
212                ..Default::default()
213            })
214            .exec(&*tx)
215            .await?
216            .last_insert_id;
217
218            Ok(flag)
219        })
220        .await
221    }
222
223    pub async fn add_user_flag(&self, user: UserId, flag: FlagId) -> Result<()> {
224        self.transaction(|tx| async move {
225            user_feature::Entity::insert(user_feature::ActiveModel {
226                user_id: ActiveValue::set(user),
227                feature_id: ActiveValue::set(flag),
228            })
229            .exec(&*tx)
230            .await?;
231
232            Ok(())
233        })
234        .await
235    }
236
237    pub async fn get_user_flags(&self, user: UserId) -> Result<Vec<String>> {
238        self.transaction(|tx| async move {
239            #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
240            enum QueryAs {
241                Flag,
242            }
243
244            let flags = user::Model {
245                id: user,
246                ..Default::default()
247            }
248            .find_linked(user::UserFlags)
249            .select_only()
250            .column(feature_flag::Column::Flag)
251            .into_values::<_, QueryAs>()
252            .all(&*tx)
253            .await?;
254
255            Ok(flags)
256        })
257        .await
258    }
259}