]> Untitled Git - lemmy.git/blob - crates/db_queries/src/source/user.rs
Move most code into crates/ subfolder
[lemmy.git] / crates / db_queries / src / source / user.rs
1 use crate::{is_email_regex, ApubObject, Crud, ToSafeSettings};
2 use bcrypt::{hash, DEFAULT_COST};
3 use diesel::{dsl::*, result::Error, *};
4 use lemmy_db_schema::{
5   naive_now,
6   schema::user_::dsl::*,
7   source::user::{UserForm, UserSafeSettings, User_},
8 };
9 use lemmy_utils::settings::Settings;
10
11 mod safe_type {
12   use crate::ToSafe;
13   use lemmy_db_schema::{schema::user_::columns::*, source::user::User_};
14
15   type Columns = (
16     id,
17     name,
18     preferred_username,
19     avatar,
20     admin,
21     banned,
22     published,
23     updated,
24     matrix_user_id,
25     actor_id,
26     bio,
27     local,
28     banner,
29     deleted,
30   );
31
32   impl ToSafe for User_ {
33     type SafeColumns = Columns;
34     fn safe_columns_tuple() -> Self::SafeColumns {
35       (
36         id,
37         name,
38         preferred_username,
39         avatar,
40         admin,
41         banned,
42         published,
43         updated,
44         matrix_user_id,
45         actor_id,
46         bio,
47         local,
48         banner,
49         deleted,
50       )
51     }
52   }
53 }
54
55 mod safe_type_alias_1 {
56   use crate::ToSafe;
57   use lemmy_db_schema::{schema::user_alias_1::columns::*, source::user::UserAlias1};
58
59   type Columns = (
60     id,
61     name,
62     preferred_username,
63     avatar,
64     admin,
65     banned,
66     published,
67     updated,
68     matrix_user_id,
69     actor_id,
70     bio,
71     local,
72     banner,
73     deleted,
74   );
75
76   impl ToSafe for UserAlias1 {
77     type SafeColumns = Columns;
78     fn safe_columns_tuple() -> Self::SafeColumns {
79       (
80         id,
81         name,
82         preferred_username,
83         avatar,
84         admin,
85         banned,
86         published,
87         updated,
88         matrix_user_id,
89         actor_id,
90         bio,
91         local,
92         banner,
93         deleted,
94       )
95     }
96   }
97 }
98
99 mod safe_type_alias_2 {
100   use crate::ToSafe;
101   use lemmy_db_schema::{schema::user_alias_2::columns::*, source::user::UserAlias2};
102
103   type Columns = (
104     id,
105     name,
106     preferred_username,
107     avatar,
108     admin,
109     banned,
110     published,
111     updated,
112     matrix_user_id,
113     actor_id,
114     bio,
115     local,
116     banner,
117     deleted,
118   );
119
120   impl ToSafe for UserAlias2 {
121     type SafeColumns = Columns;
122     fn safe_columns_tuple() -> Self::SafeColumns {
123       (
124         id,
125         name,
126         preferred_username,
127         avatar,
128         admin,
129         banned,
130         published,
131         updated,
132         matrix_user_id,
133         actor_id,
134         bio,
135         local,
136         banner,
137         deleted,
138       )
139     }
140   }
141 }
142
143 mod safe_settings_type {
144   use crate::ToSafeSettings;
145   use lemmy_db_schema::{schema::user_::columns::*, source::user::User_};
146
147   type Columns = (
148     id,
149     name,
150     preferred_username,
151     email,
152     avatar,
153     admin,
154     banned,
155     published,
156     updated,
157     show_nsfw,
158     theme,
159     default_sort_type,
160     default_listing_type,
161     lang,
162     show_avatars,
163     send_notifications_to_email,
164     matrix_user_id,
165     actor_id,
166     bio,
167     local,
168     last_refreshed_at,
169     banner,
170     deleted,
171   );
172
173   impl ToSafeSettings for User_ {
174     type SafeSettingsColumns = Columns;
175     fn safe_settings_columns_tuple() -> Self::SafeSettingsColumns {
176       (
177         id,
178         name,
179         preferred_username,
180         email,
181         avatar,
182         admin,
183         banned,
184         published,
185         updated,
186         show_nsfw,
187         theme,
188         default_sort_type,
189         default_listing_type,
190         lang,
191         show_avatars,
192         send_notifications_to_email,
193         matrix_user_id,
194         actor_id,
195         bio,
196         local,
197         last_refreshed_at,
198         banner,
199         deleted,
200       )
201     }
202   }
203 }
204
205 pub trait UserSafeSettings_ {
206   fn read(conn: &PgConnection, user_id: i32) -> Result<UserSafeSettings, Error>;
207 }
208
209 impl UserSafeSettings_ for UserSafeSettings {
210   fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
211     user_
212       .select(User_::safe_settings_columns_tuple())
213       .filter(deleted.eq(false))
214       .find(user_id)
215       .first::<Self>(conn)
216   }
217 }
218
219 impl Crud<UserForm> for User_ {
220   fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
221     user_
222       .filter(deleted.eq(false))
223       .find(user_id)
224       .first::<Self>(conn)
225   }
226   fn delete(conn: &PgConnection, user_id: i32) -> Result<usize, Error> {
227     diesel::delete(user_.find(user_id)).execute(conn)
228   }
229   fn create(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
230     insert_into(user_).values(form).get_result::<Self>(conn)
231   }
232   fn update(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result<Self, Error> {
233     diesel::update(user_.find(user_id))
234       .set(form)
235       .get_result::<Self>(conn)
236   }
237 }
238
239 impl ApubObject<UserForm> for User_ {
240   fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
241     use lemmy_db_schema::schema::user_::dsl::*;
242     user_
243       .filter(deleted.eq(false))
244       .filter(actor_id.eq(object_id))
245       .first::<Self>(conn)
246   }
247
248   fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
249     insert_into(user_)
250       .values(user_form)
251       .on_conflict(actor_id)
252       .do_update()
253       .set(user_form)
254       .get_result::<Self>(conn)
255   }
256 }
257
258 pub trait User {
259   fn register(conn: &PgConnection, form: &UserForm) -> Result<User_, Error>;
260   fn update_password(conn: &PgConnection, user_id: i32, new_password: &str)
261     -> Result<User_, Error>;
262   fn read_from_name(conn: &PgConnection, from_user_name: &str) -> Result<User_, Error>;
263   fn add_admin(conn: &PgConnection, user_id: i32, added: bool) -> Result<User_, Error>;
264   fn ban_user(conn: &PgConnection, user_id: i32, ban: bool) -> Result<User_, Error>;
265   fn find_by_email_or_username(
266     conn: &PgConnection,
267     username_or_email: &str,
268   ) -> Result<User_, Error>;
269   fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error>;
270   fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error>;
271   fn get_profile_url(&self, hostname: &str) -> String;
272   fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error>;
273   fn delete_account(conn: &PgConnection, user_id: i32) -> Result<User_, Error>;
274 }
275
276 impl User for User_ {
277   fn register(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
278     let mut edited_user = form.clone();
279     let password_hash =
280       hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
281     edited_user.password_encrypted = password_hash;
282
283     Self::create(&conn, &edited_user)
284   }
285
286   // TODO do more individual updates like these
287   fn update_password(conn: &PgConnection, user_id: i32, new_password: &str) -> Result<Self, Error> {
288     let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
289
290     diesel::update(user_.find(user_id))
291       .set((
292         password_encrypted.eq(password_hash),
293         updated.eq(naive_now()),
294       ))
295       .get_result::<Self>(conn)
296   }
297
298   fn read_from_name(conn: &PgConnection, from_user_name: &str) -> Result<Self, Error> {
299     user_
300       .filter(local.eq(true))
301       .filter(deleted.eq(false))
302       .filter(name.eq(from_user_name))
303       .first::<Self>(conn)
304   }
305
306   fn add_admin(conn: &PgConnection, user_id: i32, added: bool) -> Result<Self, Error> {
307     diesel::update(user_.find(user_id))
308       .set(admin.eq(added))
309       .get_result::<Self>(conn)
310   }
311
312   fn ban_user(conn: &PgConnection, user_id: i32, ban: bool) -> Result<Self, Error> {
313     diesel::update(user_.find(user_id))
314       .set(banned.eq(ban))
315       .get_result::<Self>(conn)
316   }
317
318   fn find_by_email_or_username(
319     conn: &PgConnection,
320     username_or_email: &str,
321   ) -> Result<Self, Error> {
322     if is_email_regex(username_or_email) {
323       Self::find_by_email(conn, username_or_email)
324     } else {
325       Self::find_by_username(conn, username_or_email)
326     }
327   }
328
329   fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error> {
330     user_
331       .filter(deleted.eq(false))
332       .filter(local.eq(true))
333       .filter(name.ilike(username))
334       .first::<User_>(conn)
335   }
336
337   fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error> {
338     user_
339       .filter(deleted.eq(false))
340       .filter(local.eq(true))
341       .filter(email.eq(from_email))
342       .first::<User_>(conn)
343   }
344
345   fn get_profile_url(&self, hostname: &str) -> String {
346     format!(
347       "{}://{}/u/{}",
348       Settings::get().get_protocol_string(),
349       hostname,
350       self.name
351     )
352   }
353
354   fn mark_as_updated(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
355     diesel::update(user_.find(user_id))
356       .set((last_refreshed_at.eq(naive_now()),))
357       .get_result::<Self>(conn)
358   }
359
360   fn delete_account(conn: &PgConnection, user_id: i32) -> Result<User_, Error> {
361     diesel::update(user_.find(user_id))
362       .set((
363         preferred_username.eq::<Option<String>>(None),
364         email.eq::<Option<String>>(None),
365         matrix_user_id.eq::<Option<String>>(None),
366         bio.eq::<Option<String>>(None),
367         deleted.eq(true),
368         updated.eq(naive_now()),
369       ))
370       .get_result::<Self>(conn)
371   }
372 }
373
374 #[cfg(test)]
375 mod tests {
376   use crate::{establish_unpooled_connection, source::user::*, ListingType, SortType};
377
378   #[test]
379   fn test_crud() {
380     let conn = establish_unpooled_connection();
381
382     let new_user = UserForm {
383       name: "thommy".into(),
384       preferred_username: None,
385       password_encrypted: "nope".into(),
386       email: None,
387       matrix_user_id: None,
388       avatar: None,
389       banner: None,
390       admin: false,
391       banned: Some(false),
392       published: None,
393       updated: None,
394       show_nsfw: false,
395       theme: "browser".into(),
396       default_sort_type: SortType::Hot as i16,
397       default_listing_type: ListingType::Subscribed as i16,
398       lang: "browser".into(),
399       show_avatars: true,
400       send_notifications_to_email: false,
401       actor_id: None,
402       bio: None,
403       local: true,
404       private_key: None,
405       public_key: None,
406       last_refreshed_at: None,
407     };
408
409     let inserted_user = User_::create(&conn, &new_user).unwrap();
410
411     let expected_user = User_ {
412       id: inserted_user.id,
413       name: "thommy".into(),
414       preferred_username: None,
415       password_encrypted: "nope".into(),
416       email: None,
417       matrix_user_id: None,
418       avatar: None,
419       banner: None,
420       admin: false,
421       banned: false,
422       published: inserted_user.published,
423       updated: None,
424       show_nsfw: false,
425       theme: "browser".into(),
426       default_sort_type: SortType::Hot as i16,
427       default_listing_type: ListingType::Subscribed as i16,
428       lang: "browser".into(),
429       show_avatars: true,
430       send_notifications_to_email: false,
431       actor_id: inserted_user.actor_id.to_owned(),
432       bio: None,
433       local: true,
434       private_key: None,
435       public_key: None,
436       last_refreshed_at: inserted_user.published,
437       deleted: false,
438     };
439
440     let read_user = User_::read(&conn, inserted_user.id).unwrap();
441     let updated_user = User_::update(&conn, inserted_user.id, &new_user).unwrap();
442     let num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
443
444     assert_eq!(expected_user, read_user);
445     assert_eq!(expected_user, inserted_user);
446     assert_eq!(expected_user, updated_user);
447     assert_eq!(1, num_deleted);
448   }
449 }