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