]> Untitled Git - lemmy.git/blob - lemmy_db/src/user.rs
Isomorphic docker (#1124)
[lemmy.git] / lemmy_db / src / user.rs
1 use crate::{
2   is_email_regex,
3   naive_now,
4   schema::{user_, user_::dsl::*},
5   Crud,
6 };
7 use bcrypt::{hash, DEFAULT_COST};
8 use diesel::{dsl::*, result::Error, *};
9 use serde::Serialize;
10
11 #[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)]
12 #[table_name = "user_"]
13 pub struct User_ {
14   pub id: i32,
15   pub name: String,
16   pub preferred_username: Option<String>,
17   pub password_encrypted: String,
18   pub email: Option<String>,
19   pub avatar: Option<String>,
20   pub admin: bool,
21   pub banned: bool,
22   pub published: chrono::NaiveDateTime,
23   pub updated: Option<chrono::NaiveDateTime>,
24   pub show_nsfw: bool,
25   pub theme: String,
26   pub default_sort_type: i16,
27   pub default_listing_type: i16,
28   pub lang: String,
29   pub show_avatars: bool,
30   pub send_notifications_to_email: bool,
31   pub matrix_user_id: Option<String>,
32   pub actor_id: String,
33   pub bio: Option<String>,
34   pub local: bool,
35   pub private_key: Option<String>,
36   pub public_key: Option<String>,
37   pub last_refreshed_at: chrono::NaiveDateTime,
38   pub banner: Option<String>,
39 }
40
41 #[derive(Insertable, AsChangeset, Clone)]
42 #[table_name = "user_"]
43 pub struct UserForm {
44   pub name: String,
45   pub preferred_username: Option<String>,
46   pub password_encrypted: String,
47   pub admin: bool,
48   pub banned: bool,
49   pub email: Option<Option<String>>,
50   pub avatar: Option<Option<String>>,
51   pub updated: Option<chrono::NaiveDateTime>,
52   pub show_nsfw: bool,
53   pub theme: String,
54   pub default_sort_type: i16,
55   pub default_listing_type: i16,
56   pub lang: String,
57   pub show_avatars: bool,
58   pub send_notifications_to_email: bool,
59   pub matrix_user_id: Option<String>,
60   pub actor_id: Option<String>,
61   pub bio: Option<String>,
62   pub local: bool,
63   pub private_key: Option<String>,
64   pub public_key: Option<String>,
65   pub last_refreshed_at: Option<chrono::NaiveDateTime>,
66   pub banner: Option<Option<String>>,
67 }
68
69 impl Crud<UserForm> for User_ {
70   fn read(conn: &PgConnection, user_id: i32) -> Result<Self, Error> {
71     user_.find(user_id).first::<Self>(conn)
72   }
73   fn delete(conn: &PgConnection, user_id: i32) -> Result<usize, Error> {
74     diesel::delete(user_.find(user_id)).execute(conn)
75   }
76   fn create(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
77     insert_into(user_).values(form).get_result::<Self>(conn)
78   }
79   fn update(conn: &PgConnection, user_id: i32, form: &UserForm) -> Result<Self, Error> {
80     diesel::update(user_.find(user_id))
81       .set(form)
82       .get_result::<Self>(conn)
83   }
84 }
85
86 impl User_ {
87   pub fn register(conn: &PgConnection, form: &UserForm) -> Result<Self, Error> {
88     let mut edited_user = form.clone();
89     let password_hash =
90       hash(&form.password_encrypted, DEFAULT_COST).expect("Couldn't hash password");
91     edited_user.password_encrypted = password_hash;
92
93     Self::create(&conn, &edited_user)
94   }
95
96   // TODO do more individual updates like these
97   pub fn update_password(
98     conn: &PgConnection,
99     user_id: i32,
100     new_password: &str,
101   ) -> Result<Self, Error> {
102     let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
103
104     diesel::update(user_.find(user_id))
105       .set((
106         password_encrypted.eq(password_hash),
107         updated.eq(naive_now()),
108       ))
109       .get_result::<Self>(conn)
110   }
111
112   pub fn read_from_name(conn: &PgConnection, from_user_name: &str) -> Result<Self, Error> {
113     user_.filter(name.eq(from_user_name)).first::<Self>(conn)
114   }
115
116   pub fn add_admin(conn: &PgConnection, user_id: i32, added: bool) -> Result<Self, Error> {
117     diesel::update(user_.find(user_id))
118       .set(admin.eq(added))
119       .get_result::<Self>(conn)
120   }
121
122   pub fn ban_user(conn: &PgConnection, user_id: i32, ban: bool) -> Result<Self, Error> {
123     diesel::update(user_.find(user_id))
124       .set(banned.eq(ban))
125       .get_result::<Self>(conn)
126   }
127
128   pub fn read_from_actor_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
129     use crate::schema::user_::dsl::*;
130     user_.filter(actor_id.eq(object_id)).first::<Self>(conn)
131   }
132
133   pub fn find_by_email_or_username(
134     conn: &PgConnection,
135     username_or_email: &str,
136   ) -> Result<Self, Error> {
137     if is_email_regex(username_or_email) {
138       Self::find_by_email(conn, username_or_email)
139     } else {
140       Self::find_by_username(conn, username_or_email)
141     }
142   }
143
144   pub fn find_by_username(conn: &PgConnection, username: &str) -> Result<User_, Error> {
145     user_.filter(name.ilike(username)).first::<User_>(conn)
146   }
147
148   pub fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<User_, Error> {
149     user_.filter(email.eq(from_email)).first::<User_>(conn)
150   }
151
152   pub fn get_profile_url(&self, hostname: &str) -> String {
153     format!("https://{}/u/{}", hostname, self.name)
154   }
155
156   pub fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result<User_, Error> {
157     insert_into(user_)
158       .values(user_form)
159       .on_conflict(actor_id)
160       .do_update()
161       .set(user_form)
162       .get_result::<Self>(conn)
163   }
164 }
165
166 #[cfg(test)]
167 mod tests {
168   use crate::{tests::establish_unpooled_connection, user::*, ListingType, SortType};
169
170   #[test]
171   fn test_crud() {
172     let conn = establish_unpooled_connection();
173
174     let new_user = UserForm {
175       name: "thommy".into(),
176       preferred_username: None,
177       password_encrypted: "nope".into(),
178       email: None,
179       matrix_user_id: None,
180       avatar: None,
181       banner: None,
182       admin: false,
183       banned: false,
184       updated: None,
185       show_nsfw: false,
186       theme: "darkly".into(),
187       default_sort_type: SortType::Hot as i16,
188       default_listing_type: ListingType::Subscribed as i16,
189       lang: "browser".into(),
190       show_avatars: true,
191       send_notifications_to_email: false,
192       actor_id: None,
193       bio: None,
194       local: true,
195       private_key: None,
196       public_key: None,
197       last_refreshed_at: None,
198     };
199
200     let inserted_user = User_::create(&conn, &new_user).unwrap();
201
202     let expected_user = User_ {
203       id: inserted_user.id,
204       name: "thommy".into(),
205       preferred_username: None,
206       password_encrypted: "nope".into(),
207       email: None,
208       matrix_user_id: None,
209       avatar: None,
210       banner: None,
211       admin: false,
212       banned: false,
213       published: inserted_user.published,
214       updated: None,
215       show_nsfw: false,
216       theme: "darkly".into(),
217       default_sort_type: SortType::Hot as i16,
218       default_listing_type: ListingType::Subscribed as i16,
219       lang: "browser".into(),
220       show_avatars: true,
221       send_notifications_to_email: false,
222       actor_id: inserted_user.actor_id.to_owned(),
223       bio: None,
224       local: true,
225       private_key: None,
226       public_key: None,
227       last_refreshed_at: inserted_user.published,
228     };
229
230     let read_user = User_::read(&conn, inserted_user.id).unwrap();
231     let updated_user = User_::update(&conn, inserted_user.id, &new_user).unwrap();
232     let num_deleted = User_::delete(&conn, inserted_user.id).unwrap();
233
234     assert_eq!(expected_user, read_user);
235     assert_eq!(expected_user, inserted_user);
236     assert_eq!(expected_user, updated_user);
237     assert_eq!(1, num_deleted);
238   }
239 }