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