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