]> Untitled Git - lemmy.git/blob - crates/db_schema/src/impls/post.rs
add enable_federated_downvotes site option
[lemmy.git] / crates / db_schema / src / impls / post.rs
1 use crate::{
2   newtypes::{CommunityId, DbUrl, PersonId, PostId},
3   schema::post::dsl::{
4     ap_id,
5     body,
6     community_id,
7     creator_id,
8     deleted,
9     featured_community,
10     name,
11     post,
12     published,
13     removed,
14     thumbnail_url,
15     updated,
16     url,
17   },
18   source::post::{
19     Post,
20     PostInsertForm,
21     PostLike,
22     PostLikeForm,
23     PostRead,
24     PostReadForm,
25     PostSaved,
26     PostSavedForm,
27     PostUpdateForm,
28   },
29   traits::{Crud, Likeable, Readable, Saveable},
30   utils::{get_conn, naive_now, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX},
31 };
32 use ::url::Url;
33 use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl, TextExpressionMethods};
34 use diesel_async::RunQueryDsl;
35
36 #[async_trait]
37 impl Crud for Post {
38   type InsertForm = PostInsertForm;
39   type UpdateForm = PostUpdateForm;
40   type IdType = PostId;
41
42   async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
43     let conn = &mut get_conn(pool).await?;
44     insert_into(post)
45       .values(form)
46       .on_conflict(ap_id)
47       .do_update()
48       .set(form)
49       .get_result::<Self>(conn)
50       .await
51   }
52
53   async fn update(
54     pool: &mut DbPool<'_>,
55     post_id: PostId,
56     new_post: &Self::UpdateForm,
57   ) -> Result<Self, Error> {
58     let conn = &mut get_conn(pool).await?;
59     diesel::update(post.find(post_id))
60       .set(new_post)
61       .get_result::<Self>(conn)
62       .await
63   }
64 }
65
66 impl Post {
67   pub async fn list_for_community(
68     pool: &mut DbPool<'_>,
69     the_community_id: CommunityId,
70   ) -> Result<Vec<Self>, Error> {
71     let conn = &mut get_conn(pool).await?;
72     post
73       .filter(community_id.eq(the_community_id))
74       .filter(deleted.eq(false))
75       .filter(removed.eq(false))
76       .then_order_by(featured_community.desc())
77       .then_order_by(published.desc())
78       .limit(FETCH_LIMIT_MAX)
79       .load::<Self>(conn)
80       .await
81   }
82
83   pub async fn list_featured_for_community(
84     pool: &mut DbPool<'_>,
85     the_community_id: CommunityId,
86   ) -> Result<Vec<Self>, Error> {
87     let conn = &mut get_conn(pool).await?;
88     post
89       .filter(community_id.eq(the_community_id))
90       .filter(deleted.eq(false))
91       .filter(removed.eq(false))
92       .filter(featured_community.eq(true))
93       .then_order_by(published.desc())
94       .limit(FETCH_LIMIT_MAX)
95       .load::<Self>(conn)
96       .await
97   }
98
99   pub async fn permadelete_for_creator(
100     pool: &mut DbPool<'_>,
101     for_creator_id: PersonId,
102   ) -> Result<Vec<Self>, Error> {
103     let conn = &mut get_conn(pool).await?;
104
105     diesel::update(post.filter(creator_id.eq(for_creator_id)))
106       .set((
107         name.eq(DELETED_REPLACEMENT_TEXT),
108         url.eq(Option::<&str>::None),
109         body.eq(DELETED_REPLACEMENT_TEXT),
110         deleted.eq(true),
111         updated.eq(naive_now()),
112       ))
113       .get_results::<Self>(conn)
114       .await
115   }
116
117   pub async fn update_removed_for_creator(
118     pool: &mut DbPool<'_>,
119     for_creator_id: PersonId,
120     for_community_id: Option<CommunityId>,
121     new_removed: bool,
122   ) -> Result<Vec<Self>, Error> {
123     let conn = &mut get_conn(pool).await?;
124
125     let mut update = diesel::update(post).into_boxed();
126     update = update.filter(creator_id.eq(for_creator_id));
127
128     if let Some(for_community_id) = for_community_id {
129       update = update.filter(community_id.eq(for_community_id));
130     }
131
132     update
133       .set((removed.eq(new_removed), updated.eq(naive_now())))
134       .get_results::<Self>(conn)
135       .await
136   }
137
138   pub fn is_post_creator(person_id: PersonId, post_creator_id: PersonId) -> bool {
139     person_id == post_creator_id
140   }
141
142   pub async fn read_from_apub_id(
143     pool: &mut DbPool<'_>,
144     object_id: Url,
145   ) -> Result<Option<Self>, Error> {
146     let conn = &mut get_conn(pool).await?;
147     let object_id: DbUrl = object_id.into();
148     Ok(
149       post
150         .filter(ap_id.eq(object_id))
151         .first::<Post>(conn)
152         .await
153         .ok()
154         .map(Into::into),
155     )
156   }
157
158   pub async fn fetch_pictrs_posts_for_creator(
159     pool: &mut DbPool<'_>,
160     for_creator_id: PersonId,
161   ) -> Result<Vec<Self>, Error> {
162     let conn = &mut get_conn(pool).await?;
163     let pictrs_search = "%pictrs/image%";
164
165     post
166       .filter(creator_id.eq(for_creator_id))
167       .filter(url.like(pictrs_search))
168       .load::<Self>(conn)
169       .await
170   }
171
172   /// Sets the url and thumbnails fields to None
173   pub async fn remove_pictrs_post_images_and_thumbnails_for_creator(
174     pool: &mut DbPool<'_>,
175     for_creator_id: PersonId,
176   ) -> Result<Vec<Self>, Error> {
177     let conn = &mut get_conn(pool).await?;
178     let pictrs_search = "%pictrs/image%";
179
180     diesel::update(
181       post
182         .filter(creator_id.eq(for_creator_id))
183         .filter(url.like(pictrs_search)),
184     )
185     .set((
186       url.eq::<Option<String>>(None),
187       thumbnail_url.eq::<Option<String>>(None),
188     ))
189     .get_results::<Self>(conn)
190     .await
191   }
192
193   pub async fn fetch_pictrs_posts_for_community(
194     pool: &mut DbPool<'_>,
195     for_community_id: CommunityId,
196   ) -> Result<Vec<Self>, Error> {
197     let conn = &mut get_conn(pool).await?;
198     let pictrs_search = "%pictrs/image%";
199     post
200       .filter(community_id.eq(for_community_id))
201       .filter(url.like(pictrs_search))
202       .load::<Self>(conn)
203       .await
204   }
205
206   /// Sets the url and thumbnails fields to None
207   pub async fn remove_pictrs_post_images_and_thumbnails_for_community(
208     pool: &mut DbPool<'_>,
209     for_community_id: CommunityId,
210   ) -> Result<Vec<Self>, Error> {
211     let conn = &mut get_conn(pool).await?;
212     let pictrs_search = "%pictrs/image%";
213
214     diesel::update(
215       post
216         .filter(community_id.eq(for_community_id))
217         .filter(url.like(pictrs_search)),
218     )
219     .set((
220       url.eq::<Option<String>>(None),
221       thumbnail_url.eq::<Option<String>>(None),
222     ))
223     .get_results::<Self>(conn)
224     .await
225   }
226 }
227
228 #[async_trait]
229 impl Likeable for PostLike {
230   type Form = PostLikeForm;
231   type IdType = PostId;
232   async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result<Self, Error> {
233     use crate::schema::post_like::dsl::{person_id, post_id, post_like};
234     let conn = &mut get_conn(pool).await?;
235     insert_into(post_like)
236       .values(post_like_form)
237       .on_conflict((post_id, person_id))
238       .do_update()
239       .set(post_like_form)
240       .get_result::<Self>(conn)
241       .await
242   }
243   async fn remove(
244     pool: &mut DbPool<'_>,
245     person_id: PersonId,
246     post_id: PostId,
247   ) -> Result<usize, Error> {
248     use crate::schema::post_like::dsl;
249     let conn = &mut get_conn(pool).await?;
250     diesel::delete(
251       dsl::post_like
252         .filter(dsl::post_id.eq(post_id))
253         .filter(dsl::person_id.eq(person_id)),
254     )
255     .execute(conn)
256     .await
257   }
258 }
259
260 #[async_trait]
261 impl Saveable for PostSaved {
262   type Form = PostSavedForm;
263   async fn save(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result<Self, Error> {
264     use crate::schema::post_saved::dsl::{person_id, post_id, post_saved};
265     let conn = &mut get_conn(pool).await?;
266     insert_into(post_saved)
267       .values(post_saved_form)
268       .on_conflict((post_id, person_id))
269       .do_update()
270       .set(post_saved_form)
271       .get_result::<Self>(conn)
272       .await
273   }
274   async fn unsave(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result<usize, Error> {
275     use crate::schema::post_saved::dsl::{person_id, post_id, post_saved};
276     let conn = &mut get_conn(pool).await?;
277     diesel::delete(
278       post_saved
279         .filter(post_id.eq(post_saved_form.post_id))
280         .filter(person_id.eq(post_saved_form.person_id)),
281     )
282     .execute(conn)
283     .await
284   }
285 }
286
287 #[async_trait]
288 impl Readable for PostRead {
289   type Form = PostReadForm;
290   async fn mark_as_read(
291     pool: &mut DbPool<'_>,
292     post_read_form: &PostReadForm,
293   ) -> Result<Self, Error> {
294     use crate::schema::post_read::dsl::{person_id, post_id, post_read};
295     let conn = &mut get_conn(pool).await?;
296     insert_into(post_read)
297       .values(post_read_form)
298       .on_conflict((post_id, person_id))
299       .do_update()
300       .set(post_read_form)
301       .get_result::<Self>(conn)
302       .await
303   }
304
305   async fn mark_as_unread(
306     pool: &mut DbPool<'_>,
307     post_read_form: &PostReadForm,
308   ) -> Result<usize, Error> {
309     use crate::schema::post_read::dsl::{person_id, post_id, post_read};
310     let conn = &mut get_conn(pool).await?;
311     diesel::delete(
312       post_read
313         .filter(post_id.eq(post_read_form.post_id))
314         .filter(person_id.eq(post_read_form.person_id)),
315     )
316     .execute(conn)
317     .await
318   }
319 }
320
321 #[cfg(test)]
322 mod tests {
323   #![allow(clippy::unwrap_used)]
324   #![allow(clippy::indexing_slicing)]
325
326   use crate::{
327     source::{
328       community::{Community, CommunityInsertForm},
329       instance::Instance,
330       person::{Person, PersonInsertForm},
331       post::{
332         Post,
333         PostInsertForm,
334         PostLike,
335         PostLikeForm,
336         PostRead,
337         PostReadForm,
338         PostSaved,
339         PostSavedForm,
340         PostUpdateForm,
341       },
342     },
343     traits::{Crud, Likeable, Readable, Saveable},
344     utils::build_db_pool_for_tests,
345   };
346   use serial_test::serial;
347
348   #[tokio::test]
349   #[serial]
350   async fn test_crud() {
351     let pool = &build_db_pool_for_tests().await;
352     let pool = &mut pool.into();
353
354     let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
355       .await
356       .unwrap();
357
358     let new_person = PersonInsertForm::builder()
359       .name("jim".into())
360       .public_key("pubkey".to_string())
361       .instance_id(inserted_instance.id)
362       .build();
363
364     let inserted_person = Person::create(pool, &new_person).await.unwrap();
365
366     let new_community = CommunityInsertForm::builder()
367       .name("test community_3".to_string())
368       .title("nada".to_owned())
369       .public_key("pubkey".to_string())
370       .instance_id(inserted_instance.id)
371       .build();
372
373     let inserted_community = Community::create(pool, &new_community).await.unwrap();
374
375     let new_post = PostInsertForm::builder()
376       .name("A test post".into())
377       .creator_id(inserted_person.id)
378       .community_id(inserted_community.id)
379       .build();
380
381     let inserted_post = Post::create(pool, &new_post).await.unwrap();
382
383     let expected_post = Post {
384       id: inserted_post.id,
385       name: "A test post".into(),
386       url: None,
387       body: None,
388       creator_id: inserted_person.id,
389       community_id: inserted_community.id,
390       published: inserted_post.published,
391       removed: false,
392       locked: false,
393       nsfw: false,
394       deleted: false,
395       updated: None,
396       embed_title: None,
397       embed_description: None,
398       embed_video_url: None,
399       thumbnail_url: None,
400       ap_id: inserted_post.ap_id.clone(),
401       local: true,
402       language_id: Default::default(),
403       featured_community: false,
404       featured_local: false,
405     };
406
407     // Post Like
408     let post_like_form = PostLikeForm {
409       post_id: inserted_post.id,
410       person_id: inserted_person.id,
411       score: 1,
412     };
413
414     let inserted_post_like = PostLike::like(pool, &post_like_form).await.unwrap();
415
416     let expected_post_like = PostLike {
417       id: inserted_post_like.id,
418       post_id: inserted_post.id,
419       person_id: inserted_person.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       person_id: inserted_person.id,
428     };
429
430     let inserted_post_saved = PostSaved::save(pool, &post_saved_form).await.unwrap();
431
432     let expected_post_saved = PostSaved {
433       id: inserted_post_saved.id,
434       post_id: inserted_post.id,
435       person_id: inserted_person.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       person_id: inserted_person.id,
443     };
444
445     let inserted_post_read = PostRead::mark_as_read(pool, &post_read_form).await.unwrap();
446
447     let expected_post_read = PostRead {
448       id: inserted_post_read.id,
449       post_id: inserted_post.id,
450       person_id: inserted_person.id,
451       published: inserted_post_read.published,
452     };
453
454     let read_post = Post::read(pool, inserted_post.id).await.unwrap();
455
456     let new_post_update = PostUpdateForm {
457       name: Some("A test post".into()),
458       ..Default::default()
459     };
460     let updated_post = Post::update(pool, inserted_post.id, &new_post_update)
461       .await
462       .unwrap();
463
464     let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id)
465       .await
466       .unwrap();
467     let saved_removed = PostSaved::unsave(pool, &post_saved_form).await.unwrap();
468     let read_removed = PostRead::mark_as_unread(pool, &post_read_form)
469       .await
470       .unwrap();
471     let num_deleted = Post::delete(pool, inserted_post.id).await.unwrap();
472     Community::delete(pool, inserted_community.id)
473       .await
474       .unwrap();
475     Person::delete(pool, inserted_person.id).await.unwrap();
476     Instance::delete(pool, inserted_instance.id).await.unwrap();
477
478     assert_eq!(expected_post, read_post);
479     assert_eq!(expected_post, inserted_post);
480     assert_eq!(expected_post, updated_post);
481     assert_eq!(expected_post_like, inserted_post_like);
482     assert_eq!(expected_post_saved, inserted_post_saved);
483     assert_eq!(expected_post_read, inserted_post_read);
484     assert_eq!(1, like_removed);
485     assert_eq!(1, saved_removed);
486     assert_eq!(1, read_removed);
487     assert_eq!(1, num_deleted);
488   }
489 }