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