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