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