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