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