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