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