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}