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