]> Untitled Git - lemmy.git/blob - server/lemmy_db/src/post.rs
Merge remote-tracking branch 'weblate/main' into main
[lemmy.git] / server / lemmy_db / src / post.rs
1 use crate::{
2   naive_now,
3   schema::{post, post_like, post_read, post_saved},
4   Crud,
5   Likeable,
6   Readable,
7   Saveable,
8 };
9 use diesel::{dsl::*, result::Error, *};
10 use serde::{Deserialize, Serialize};
11 use url::{ParseError, Url};
12
13 #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
14 #[table_name = "post"]
15 pub struct Post {
16   pub id: i32,
17   pub name: String,
18   pub url: Option<String>,
19   pub body: Option<String>,
20   pub creator_id: i32,
21   pub community_id: i32,
22   pub removed: bool,
23   pub locked: bool,
24   pub published: chrono::NaiveDateTime,
25   pub updated: Option<chrono::NaiveDateTime>,
26   pub deleted: bool,
27   pub nsfw: bool,
28   pub stickied: bool,
29   pub embed_title: Option<String>,
30   pub embed_description: Option<String>,
31   pub embed_html: Option<String>,
32   pub thumbnail_url: Option<String>,
33   pub ap_id: String,
34   pub local: bool,
35 }
36
37 #[derive(Insertable, AsChangeset, Clone, Debug)]
38 #[table_name = "post"]
39 pub struct PostForm {
40   pub name: String,
41   pub url: Option<String>,
42   pub body: Option<String>,
43   pub creator_id: i32,
44   pub community_id: i32,
45   pub removed: Option<bool>,
46   pub locked: Option<bool>,
47   pub published: Option<chrono::NaiveDateTime>,
48   pub updated: Option<chrono::NaiveDateTime>,
49   pub deleted: Option<bool>,
50   pub nsfw: bool,
51   pub stickied: Option<bool>,
52   pub embed_title: Option<String>,
53   pub embed_description: Option<String>,
54   pub embed_html: Option<String>,
55   pub thumbnail_url: Option<String>,
56   pub ap_id: String,
57   pub local: bool,
58 }
59
60 impl PostForm {
61   pub fn get_ap_id(&self) -> Result<Url, ParseError> {
62     Url::parse(&self.ap_id)
63   }
64 }
65
66 impl Post {
67   pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
68     use crate::schema::post::dsl::*;
69     post.filter(id.eq(post_id)).first::<Self>(conn)
70   }
71
72   pub fn list_for_community(
73     conn: &PgConnection,
74     the_community_id: i32,
75   ) -> Result<Vec<Self>, Error> {
76     use crate::schema::post::dsl::*;
77     post
78       .filter(community_id.eq(the_community_id))
79       .then_order_by(published.desc())
80       .then_order_by(stickied.desc())
81       .limit(20)
82       .load::<Self>(conn)
83   }
84
85   pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
86     use crate::schema::post::dsl::*;
87     post.filter(ap_id.eq(object_id)).first::<Self>(conn)
88   }
89
90   pub fn update_ap_id(conn: &PgConnection, post_id: i32, apub_id: String) -> Result<Self, Error> {
91     use crate::schema::post::dsl::*;
92
93     diesel::update(post.find(post_id))
94       .set(ap_id.eq(apub_id))
95       .get_result::<Self>(conn)
96   }
97
98   pub fn permadelete(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
99     use crate::schema::post::dsl::*;
100
101     let perma_deleted = "*Permananently Deleted*";
102     let perma_deleted_url = "https://deleted.com";
103
104     diesel::update(post.find(post_id))
105       .set((
106         name.eq(perma_deleted),
107         url.eq(perma_deleted_url),
108         body.eq(perma_deleted),
109         deleted.eq(true),
110         updated.eq(naive_now()),
111       ))
112       .get_result::<Self>(conn)
113   }
114
115   pub fn update_deleted(
116     conn: &PgConnection,
117     post_id: i32,
118     new_deleted: bool,
119   ) -> Result<Self, Error> {
120     use crate::schema::post::dsl::*;
121     diesel::update(post.find(post_id))
122       .set((deleted.eq(new_deleted), updated.eq(naive_now())))
123       .get_result::<Self>(conn)
124   }
125
126   pub fn update_removed(
127     conn: &PgConnection,
128     post_id: i32,
129     new_removed: bool,
130   ) -> Result<Self, Error> {
131     use crate::schema::post::dsl::*;
132     diesel::update(post.find(post_id))
133       .set((removed.eq(new_removed), updated.eq(naive_now())))
134       .get_result::<Self>(conn)
135   }
136
137   pub fn update_locked(conn: &PgConnection, post_id: i32, new_locked: bool) -> Result<Self, Error> {
138     use crate::schema::post::dsl::*;
139     diesel::update(post.find(post_id))
140       .set(locked.eq(new_locked))
141       .get_result::<Self>(conn)
142   }
143
144   pub fn update_stickied(
145     conn: &PgConnection,
146     post_id: i32,
147     new_stickied: bool,
148   ) -> Result<Self, Error> {
149     use crate::schema::post::dsl::*;
150     diesel::update(post.find(post_id))
151       .set(stickied.eq(new_stickied))
152       .get_result::<Self>(conn)
153   }
154
155   pub fn is_post_creator(user_id: i32, post_creator_id: i32) -> bool {
156     user_id == post_creator_id
157   }
158 }
159
160 impl Crud<PostForm> for Post {
161   fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
162     use crate::schema::post::dsl::*;
163     post.find(post_id).first::<Self>(conn)
164   }
165
166   fn delete(conn: &PgConnection, post_id: i32) -> Result<usize, Error> {
167     use crate::schema::post::dsl::*;
168     diesel::delete(post.find(post_id)).execute(conn)
169   }
170
171   fn create(conn: &PgConnection, new_post: &PostForm) -> Result<Self, Error> {
172     use crate::schema::post::dsl::*;
173     insert_into(post).values(new_post).get_result::<Self>(conn)
174   }
175
176   fn update(conn: &PgConnection, post_id: i32, new_post: &PostForm) -> Result<Self, Error> {
177     use crate::schema::post::dsl::*;
178     diesel::update(post.find(post_id))
179       .set(new_post)
180       .get_result::<Self>(conn)
181   }
182 }
183
184 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
185 #[belongs_to(Post)]
186 #[table_name = "post_like"]
187 pub struct PostLike {
188   pub id: i32,
189   pub post_id: i32,
190   pub user_id: i32,
191   pub score: i16,
192   pub published: chrono::NaiveDateTime,
193 }
194
195 #[derive(Insertable, AsChangeset, Clone)]
196 #[table_name = "post_like"]
197 pub struct PostLikeForm {
198   pub post_id: i32,
199   pub user_id: i32,
200   pub score: i16,
201 }
202
203 impl Likeable<PostLikeForm> for PostLike {
204   fn read(conn: &PgConnection, post_id_from: i32) -> Result<Vec<Self>, Error> {
205     use crate::schema::post_like::dsl::*;
206     post_like
207       .filter(post_id.eq(post_id_from))
208       .load::<Self>(conn)
209   }
210   fn like(conn: &PgConnection, post_like_form: &PostLikeForm) -> Result<Self, Error> {
211     use crate::schema::post_like::dsl::*;
212     insert_into(post_like)
213       .values(post_like_form)
214       .get_result::<Self>(conn)
215   }
216   fn remove(conn: &PgConnection, post_like_form: &PostLikeForm) -> Result<usize, Error> {
217     use crate::schema::post_like::dsl::*;
218     diesel::delete(
219       post_like
220         .filter(post_id.eq(post_like_form.post_id))
221         .filter(user_id.eq(post_like_form.user_id)),
222     )
223     .execute(conn)
224   }
225 }
226
227 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
228 #[belongs_to(Post)]
229 #[table_name = "post_saved"]
230 pub struct PostSaved {
231   pub id: i32,
232   pub post_id: i32,
233   pub user_id: i32,
234   pub published: chrono::NaiveDateTime,
235 }
236
237 #[derive(Insertable, AsChangeset, Clone)]
238 #[table_name = "post_saved"]
239 pub struct PostSavedForm {
240   pub post_id: i32,
241   pub user_id: i32,
242 }
243
244 impl Saveable<PostSavedForm> for PostSaved {
245   fn save(conn: &PgConnection, post_saved_form: &PostSavedForm) -> Result<Self, Error> {
246     use crate::schema::post_saved::dsl::*;
247     insert_into(post_saved)
248       .values(post_saved_form)
249       .get_result::<Self>(conn)
250   }
251   fn unsave(conn: &PgConnection, post_saved_form: &PostSavedForm) -> Result<usize, Error> {
252     use crate::schema::post_saved::dsl::*;
253     diesel::delete(
254       post_saved
255         .filter(post_id.eq(post_saved_form.post_id))
256         .filter(user_id.eq(post_saved_form.user_id)),
257     )
258     .execute(conn)
259   }
260 }
261
262 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
263 #[belongs_to(Post)]
264 #[table_name = "post_read"]
265 pub struct PostRead {
266   pub id: i32,
267   pub post_id: i32,
268   pub user_id: i32,
269   pub published: chrono::NaiveDateTime,
270 }
271
272 #[derive(Insertable, AsChangeset, Clone)]
273 #[table_name = "post_read"]
274 pub struct PostReadForm {
275   pub post_id: i32,
276   pub user_id: i32,
277 }
278
279 impl Readable<PostReadForm> for PostRead {
280   fn mark_as_read(conn: &PgConnection, post_read_form: &PostReadForm) -> Result<Self, Error> {
281     use crate::schema::post_read::dsl::*;
282     insert_into(post_read)
283       .values(post_read_form)
284       .get_result::<Self>(conn)
285   }
286   fn mark_as_unread(conn: &PgConnection, post_read_form: &PostReadForm) -> Result<usize, Error> {
287     use crate::schema::post_read::dsl::*;
288     diesel::delete(
289       post_read
290         .filter(post_id.eq(post_read_form.post_id))
291         .filter(user_id.eq(post_read_form.user_id)),
292     )
293     .execute(conn)
294   }
295 }
296
297 #[cfg(test)]
298 mod tests {
299   use crate::{
300     community::*,
301     post::*,
302     tests::establish_unpooled_connection,
303     user::*,
304     ListingType,
305     SortType,
306   };
307
308   #[test]
309   fn test_crud() {
310     let conn = establish_unpooled_connection();
311
312     let new_user = UserForm {
313       name: "jim".into(),
314       preferred_username: None,
315       password_encrypted: "nope".into(),
316       email: None,
317       matrix_user_id: None,
318       avatar: None,
319       banner: None,
320       admin: false,
321       banned: false,
322       updated: None,
323       show_nsfw: false,
324       theme: "darkly".into(),
325       default_sort_type: SortType::Hot as i16,
326       default_listing_type: ListingType::Subscribed as i16,
327       lang: "browser".into(),
328       show_avatars: true,
329       send_notifications_to_email: false,
330       actor_id: "changeme_8292683678".into(),
331       bio: None,
332       local: true,
333       private_key: None,
334       public_key: None,
335       last_refreshed_at: None,
336     };
337
338     let inserted_user = User_::create(&conn, &new_user).unwrap();
339
340     let new_community = CommunityForm {
341       name: "test community_3".to_string(),
342       title: "nada".to_owned(),
343       description: None,
344       category_id: 1,
345       creator_id: inserted_user.id,
346       removed: None,
347       deleted: None,
348       updated: None,
349       nsfw: false,
350       actor_id: "changeme_8223262378".into(),
351       local: true,
352       private_key: None,
353       public_key: None,
354       last_refreshed_at: None,
355       published: None,
356       icon: None,
357       banner: None,
358     };
359
360     let inserted_community = Community::create(&conn, &new_community).unwrap();
361
362     let new_post = PostForm {
363       name: "A test post".into(),
364       url: None,
365       body: None,
366       creator_id: inserted_user.id,
367       community_id: inserted_community.id,
368       removed: None,
369       deleted: None,
370       locked: None,
371       stickied: None,
372       nsfw: false,
373       updated: None,
374       embed_title: None,
375       embed_description: None,
376       embed_html: None,
377       thumbnail_url: None,
378       ap_id: "http://fake.com".into(),
379       local: true,
380       published: None,
381     };
382
383     let inserted_post = Post::create(&conn, &new_post).unwrap();
384
385     let expected_post = Post {
386       id: inserted_post.id,
387       name: "A test post".into(),
388       url: None,
389       body: None,
390       creator_id: inserted_user.id,
391       community_id: inserted_community.id,
392       published: inserted_post.published,
393       removed: false,
394       locked: false,
395       stickied: false,
396       nsfw: false,
397       deleted: false,
398       updated: None,
399       embed_title: None,
400       embed_description: None,
401       embed_html: None,
402       thumbnail_url: None,
403       ap_id: "http://fake.com".into(),
404       local: true,
405     };
406
407     // Post Like
408     let post_like_form = PostLikeForm {
409       post_id: inserted_post.id,
410       user_id: inserted_user.id,
411       score: 1,
412     };
413
414     let inserted_post_like = PostLike::like(&conn, &post_like_form).unwrap();
415
416     let expected_post_like = PostLike {
417       id: inserted_post_like.id,
418       post_id: inserted_post.id,
419       user_id: inserted_user.id,
420       published: inserted_post_like.published,
421       score: 1,
422     };
423
424     // Post Save
425     let post_saved_form = PostSavedForm {
426       post_id: inserted_post.id,
427       user_id: inserted_user.id,
428     };
429
430     let inserted_post_saved = PostSaved::save(&conn, &post_saved_form).unwrap();
431
432     let expected_post_saved = PostSaved {
433       id: inserted_post_saved.id,
434       post_id: inserted_post.id,
435       user_id: inserted_user.id,
436       published: inserted_post_saved.published,
437     };
438
439     // Post Read
440     let post_read_form = PostReadForm {
441       post_id: inserted_post.id,
442       user_id: inserted_user.id,
443     };
444
445     let inserted_post_read = PostRead::mark_as_read(&conn, &post_read_form).unwrap();
446
447     let expected_post_read = PostRead {
448       id: inserted_post_read.id,
449       post_id: inserted_post.id,
450       user_id: inserted_user.id,
451       published: inserted_post_read.published,
452     };
453
454     let read_post = Post::read(&conn, inserted_post.id).unwrap();
455     let updated_post = Post::update(&conn, inserted_post.id, &new_post).unwrap();
456     let like_removed = PostLike::remove(&conn, &post_like_form).unwrap();
457     let saved_removed = PostSaved::unsave(&conn, &post_saved_form).unwrap();
458     let read_removed = PostRead::mark_as_unread(&conn, &post_read_form).unwrap();
459     let num_deleted = Post::delete(&conn, inserted_post.id).unwrap();
460     Community::delete(&conn, inserted_community.id).unwrap();
461     User_::delete(&conn, inserted_user.id).unwrap();
462
463     assert_eq!(expected_post, read_post);
464     assert_eq!(expected_post, inserted_post);
465     assert_eq!(expected_post, updated_post);
466     assert_eq!(expected_post_like, inserted_post_like);
467     assert_eq!(expected_post_saved, inserted_post_saved);
468     assert_eq!(expected_post_read, inserted_post_read);
469     assert_eq!(1, like_removed);
470     assert_eq!(1, saved_removed);
471     assert_eq!(1, read_removed);
472     assert_eq!(1, num_deleted);
473   }
474 }