]> Untitled Git - lemmy.git/blob - crates/db_views/src/post_view.rs
778cf98a3d4ddb158ed51e986e11027eb6784eee
[lemmy.git] / crates / db_views / src / post_view.rs
1 use crate::structs::PostView;
2 use diesel::{dsl::*, pg::Pg, result::Error, *};
3 use lemmy_db_schema::{
4   aggregates::structs::PostAggregates,
5   newtypes::{CommunityId, DbUrl, LocalUserId, PersonId, PostId},
6   schema::{
7     community,
8     community_block,
9     community_follower,
10     community_person_ban,
11     local_user_language,
12     person,
13     person_block,
14     post,
15     post_aggregates,
16     post_like,
17     post_read,
18     post_saved,
19   },
20   source::{
21     community::{Community, CommunityFollower, CommunityPersonBan, CommunitySafe},
22     local_user::LocalUser,
23     person::{Person, PersonSafe},
24     person_block::PersonBlock,
25     post::{Post, PostRead, PostSaved},
26   },
27   traits::{ToSafe, ViewToVec},
28   utils::{functions::hot_rank, fuzzy_search, limit_and_offset},
29   ListingType,
30   SortType,
31 };
32 use tracing::debug;
33 use typed_builder::TypedBuilder;
34
35 type PostViewTuple = (
36   Post,
37   PersonSafe,
38   CommunitySafe,
39   Option<CommunityPersonBan>,
40   PostAggregates,
41   Option<CommunityFollower>,
42   Option<PostSaved>,
43   Option<PostRead>,
44   Option<PersonBlock>,
45   Option<i16>,
46 );
47
48 impl PostView {
49   pub fn read(
50     conn: &mut PgConnection,
51     post_id: PostId,
52     my_person_id: Option<PersonId>,
53   ) -> Result<Self, Error> {
54     // The left join below will return None in this case
55     let person_id_join = my_person_id.unwrap_or(PersonId(-1));
56     let (
57       post,
58       creator,
59       community,
60       creator_banned_from_community,
61       counts,
62       follower,
63       saved,
64       read,
65       creator_blocked,
66       post_like,
67     ) = post::table
68       .find(post_id)
69       .inner_join(person::table)
70       .inner_join(community::table)
71       .left_join(
72         community_person_ban::table.on(
73           post::community_id
74             .eq(community_person_ban::community_id)
75             .and(community_person_ban::person_id.eq(post::creator_id))
76             .and(
77               community_person_ban::expires
78                 .is_null()
79                 .or(community_person_ban::expires.gt(now)),
80             ),
81         ),
82       )
83       .inner_join(post_aggregates::table)
84       .left_join(
85         community_follower::table.on(
86           post::community_id
87             .eq(community_follower::community_id)
88             .and(community_follower::person_id.eq(person_id_join)),
89         ),
90       )
91       .left_join(
92         post_saved::table.on(
93           post::id
94             .eq(post_saved::post_id)
95             .and(post_saved::person_id.eq(person_id_join)),
96         ),
97       )
98       .left_join(
99         post_read::table.on(
100           post::id
101             .eq(post_read::post_id)
102             .and(post_read::person_id.eq(person_id_join)),
103         ),
104       )
105       .left_join(
106         person_block::table.on(
107           post::creator_id
108             .eq(person_block::target_id)
109             .and(person_block::person_id.eq(person_id_join)),
110         ),
111       )
112       .left_join(
113         post_like::table.on(
114           post::id
115             .eq(post_like::post_id)
116             .and(post_like::person_id.eq(person_id_join)),
117         ),
118       )
119       .select((
120         post::all_columns,
121         Person::safe_columns_tuple(),
122         Community::safe_columns_tuple(),
123         community_person_ban::all_columns.nullable(),
124         post_aggregates::all_columns,
125         community_follower::all_columns.nullable(),
126         post_saved::all_columns.nullable(),
127         post_read::all_columns.nullable(),
128         person_block::all_columns.nullable(),
129         post_like::score.nullable(),
130       ))
131       .first::<PostViewTuple>(conn)?;
132
133     // If a person is given, then my_vote, if None, should be 0, not null
134     // Necessary to differentiate between other person's votes
135     let my_vote = if my_person_id.is_some() && post_like.is_none() {
136       Some(0)
137     } else {
138       post_like
139     };
140
141     Ok(PostView {
142       post,
143       creator,
144       community,
145       creator_banned_from_community: creator_banned_from_community.is_some(),
146       counts,
147       subscribed: CommunityFollower::to_subscribed_type(&follower),
148       saved: saved.is_some(),
149       read: read.is_some(),
150       creator_blocked: creator_blocked.is_some(),
151       my_vote,
152     })
153   }
154 }
155
156 #[derive(TypedBuilder)]
157 #[builder(field_defaults(default))]
158 pub struct PostQuery<'a> {
159   #[builder(!default)]
160   conn: &'a mut PgConnection,
161   listing_type: Option<ListingType>,
162   sort: Option<SortType>,
163   creator_id: Option<PersonId>,
164   community_id: Option<CommunityId>,
165   community_actor_id: Option<DbUrl>,
166   local_user: Option<&'a LocalUser>,
167   search_term: Option<String>,
168   url_search: Option<String>,
169   saved_only: Option<bool>,
170   page: Option<i64>,
171   limit: Option<i64>,
172 }
173
174 impl<'a> PostQuery<'a> {
175   pub fn list(self) -> Result<Vec<PostView>, Error> {
176     use diesel::dsl::*;
177
178     // The left join below will return None in this case
179     let person_id_join = self.local_user.map(|l| l.person_id).unwrap_or(PersonId(-1));
180     let local_user_id_join = self.local_user.map(|l| l.id).unwrap_or(LocalUserId(-1));
181
182     let mut query = post::table
183       .inner_join(person::table)
184       .inner_join(community::table)
185       .left_join(
186         community_person_ban::table.on(
187           post::community_id
188             .eq(community_person_ban::community_id)
189             .and(community_person_ban::person_id.eq(post::creator_id))
190             .and(
191               community_person_ban::expires
192                 .is_null()
193                 .or(community_person_ban::expires.gt(now)),
194             ),
195         ),
196       )
197       .inner_join(post_aggregates::table)
198       .left_join(
199         community_follower::table.on(
200           post::community_id
201             .eq(community_follower::community_id)
202             .and(community_follower::person_id.eq(person_id_join)),
203         ),
204       )
205       .left_join(
206         post_saved::table.on(
207           post::id
208             .eq(post_saved::post_id)
209             .and(post_saved::person_id.eq(person_id_join)),
210         ),
211       )
212       .left_join(
213         post_read::table.on(
214           post::id
215             .eq(post_read::post_id)
216             .and(post_read::person_id.eq(person_id_join)),
217         ),
218       )
219       .left_join(
220         person_block::table.on(
221           post::creator_id
222             .eq(person_block::target_id)
223             .and(person_block::person_id.eq(person_id_join)),
224         ),
225       )
226       .left_join(
227         community_block::table.on(
228           community::id
229             .eq(community_block::community_id)
230             .and(community_block::person_id.eq(person_id_join)),
231         ),
232       )
233       .left_join(
234         post_like::table.on(
235           post::id
236             .eq(post_like::post_id)
237             .and(post_like::person_id.eq(person_id_join)),
238         ),
239       )
240       .left_join(
241         local_user_language::table.on(
242           post::language_id
243             .eq(local_user_language::language_id)
244             .and(local_user_language::local_user_id.eq(local_user_id_join)),
245         ),
246       )
247       .select((
248         post::all_columns,
249         Person::safe_columns_tuple(),
250         Community::safe_columns_tuple(),
251         community_person_ban::all_columns.nullable(),
252         post_aggregates::all_columns,
253         community_follower::all_columns.nullable(),
254         post_saved::all_columns.nullable(),
255         post_read::all_columns.nullable(),
256         person_block::all_columns.nullable(),
257         post_like::score.nullable(),
258       ))
259       .into_boxed();
260
261     if let Some(listing_type) = self.listing_type {
262       match listing_type {
263         ListingType::Subscribed => {
264           query = query.filter(community_follower::person_id.is_not_null())
265         }
266         ListingType::Local => {
267           query = query.filter(community::local.eq(true)).filter(
268             community::hidden
269               .eq(false)
270               .or(community_follower::person_id.eq(person_id_join)),
271           );
272         }
273         ListingType::All => {
274           query = query.filter(
275             community::hidden
276               .eq(false)
277               .or(community_follower::person_id.eq(person_id_join)),
278           )
279         }
280       }
281     }
282
283     if let Some(community_id) = self.community_id {
284       query = query
285         .filter(post::community_id.eq(community_id))
286         .then_order_by(post_aggregates::stickied.desc());
287     }
288
289     if let Some(community_actor_id) = self.community_actor_id {
290       query = query
291         .filter(community::actor_id.eq(community_actor_id))
292         .then_order_by(post_aggregates::stickied.desc());
293     }
294
295     if let Some(url_search) = self.url_search {
296       query = query.filter(post::url.eq(url_search));
297     }
298
299     if let Some(search_term) = self.search_term {
300       let searcher = fuzzy_search(&search_term);
301       query = query.filter(
302         post::name
303           .ilike(searcher.to_owned())
304           .or(post::body.ilike(searcher)),
305       );
306     }
307
308     // If its for a specific person, show the removed / deleted
309     if let Some(creator_id) = self.creator_id {
310       query = query.filter(post::creator_id.eq(creator_id));
311     }
312
313     if !self.local_user.map(|l| l.show_nsfw).unwrap_or(false) {
314       query = query
315         .filter(post::nsfw.eq(false))
316         .filter(community::nsfw.eq(false));
317     };
318
319     if !self.local_user.map(|l| l.show_bot_accounts).unwrap_or(true) {
320       query = query.filter(person::bot_account.eq(false));
321     };
322
323     if self.saved_only.unwrap_or(false) {
324       query = query.filter(post_saved::id.is_not_null());
325     }
326     // Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read
327     // setting wont be able to see saved posts.
328     else if !self.local_user.map(|l| l.show_read_posts).unwrap_or(true) {
329       query = query.filter(post_read::id.is_null());
330     }
331
332     if self.local_user.is_some() {
333       // Filter out the rows with missing languages
334       query = query.filter(local_user_language::id.is_not_null());
335
336       // Don't show blocked communities or persons
337       query = query.filter(community_block::person_id.is_null());
338       query = query.filter(person_block::person_id.is_null());
339     }
340
341     query = match self.sort.unwrap_or(SortType::Hot) {
342       SortType::Active => query
343         .then_order_by(
344           hot_rank(
345             post_aggregates::score,
346             post_aggregates::newest_comment_time_necro,
347           )
348           .desc(),
349         )
350         .then_order_by(post_aggregates::newest_comment_time_necro.desc()),
351       SortType::Hot => query
352         .then_order_by(hot_rank(post_aggregates::score, post_aggregates::published).desc())
353         .then_order_by(post_aggregates::published.desc()),
354       SortType::New => query.then_order_by(post_aggregates::published.desc()),
355       SortType::Old => query.then_order_by(post_aggregates::published.asc()),
356       SortType::NewComments => query.then_order_by(post_aggregates::newest_comment_time.desc()),
357       SortType::MostComments => query
358         .then_order_by(post_aggregates::comments.desc())
359         .then_order_by(post_aggregates::published.desc()),
360       SortType::TopAll => query
361         .then_order_by(post_aggregates::score.desc())
362         .then_order_by(post_aggregates::published.desc()),
363       SortType::TopYear => query
364         .filter(post_aggregates::published.gt(now - 1.years()))
365         .then_order_by(post_aggregates::score.desc())
366         .then_order_by(post_aggregates::published.desc()),
367       SortType::TopMonth => query
368         .filter(post_aggregates::published.gt(now - 1.months()))
369         .then_order_by(post_aggregates::score.desc())
370         .then_order_by(post_aggregates::published.desc()),
371       SortType::TopWeek => query
372         .filter(post_aggregates::published.gt(now - 1.weeks()))
373         .then_order_by(post_aggregates::score.desc())
374         .then_order_by(post_aggregates::published.desc()),
375       SortType::TopDay => query
376         .filter(post_aggregates::published.gt(now - 1.days()))
377         .then_order_by(post_aggregates::score.desc())
378         .then_order_by(post_aggregates::published.desc()),
379     };
380
381     let (limit, offset) = limit_and_offset(self.page, self.limit)?;
382
383     query = query
384       .limit(limit)
385       .offset(offset)
386       .filter(post::removed.eq(false))
387       .filter(post::deleted.eq(false))
388       .filter(community::removed.eq(false))
389       .filter(community::deleted.eq(false));
390
391     debug!("Post View Query: {:?}", debug_query::<Pg, _>(&query));
392
393     let res = query.load::<PostViewTuple>(self.conn)?;
394
395     Ok(PostView::from_tuple_to_vec(res))
396   }
397 }
398
399 impl ViewToVec for PostView {
400   type DbTuple = PostViewTuple;
401   fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
402     items
403       .into_iter()
404       .map(|a| Self {
405         post: a.0,
406         creator: a.1,
407         community: a.2,
408         creator_banned_from_community: a.3.is_some(),
409         counts: a.4,
410         subscribed: CommunityFollower::to_subscribed_type(&a.5),
411         saved: a.6.is_some(),
412         read: a.7.is_some(),
413         creator_blocked: a.8.is_some(),
414         my_vote: a.9,
415       })
416       .collect::<Vec<Self>>()
417   }
418 }
419
420 #[cfg(test)]
421 mod tests {
422   use crate::post_view::{PostQuery, PostView};
423   use diesel::PgConnection;
424   use lemmy_db_schema::{
425     aggregates::structs::PostAggregates,
426     newtypes::LanguageId,
427     source::{
428       community::*,
429       community_block::{CommunityBlock, CommunityBlockForm},
430       language::Language,
431       local_user::{LocalUser, LocalUserForm},
432       local_user_language::LocalUserLanguage,
433       person::*,
434       person_block::{PersonBlock, PersonBlockForm},
435       post::*,
436     },
437     traits::{Blockable, Crud, Likeable},
438     utils::establish_unpooled_connection,
439     SortType,
440     SubscribedType,
441   };
442   use serial_test::serial;
443
444   struct Data {
445     inserted_person: Person,
446     inserted_local_user: LocalUser,
447     inserted_blocked_person: Person,
448     inserted_bot: Person,
449     inserted_community: Community,
450     inserted_post: Post,
451   }
452
453   fn init_data(conn: &mut PgConnection) -> Data {
454     let person_name = "tegan".to_string();
455
456     let new_person = PersonForm {
457       name: person_name.to_owned(),
458       public_key: Some("pubkey".to_string()),
459       ..PersonForm::default()
460     };
461
462     let inserted_person = Person::create(conn, &new_person).unwrap();
463
464     let local_user_form = LocalUserForm {
465       person_id: Some(inserted_person.id),
466       password_encrypted: Some("".to_string()),
467       ..Default::default()
468     };
469     let inserted_local_user = LocalUser::create(conn, &local_user_form).unwrap();
470
471     let new_bot = PersonForm {
472       name: "mybot".to_string(),
473       bot_account: Some(true),
474       public_key: Some("pubkey".to_string()),
475       ..PersonForm::default()
476     };
477
478     let inserted_bot = Person::create(conn, &new_bot).unwrap();
479
480     let new_community = CommunityForm {
481       name: "test_community_3".to_string(),
482       title: "nada".to_owned(),
483       public_key: Some("pubkey".to_string()),
484       ..CommunityForm::default()
485     };
486
487     let inserted_community = Community::create(conn, &new_community).unwrap();
488
489     // Test a person block, make sure the post query doesn't include their post
490     let blocked_person = PersonForm {
491       name: person_name,
492       public_key: Some("pubkey".to_string()),
493       ..PersonForm::default()
494     };
495
496     let inserted_blocked_person = Person::create(conn, &blocked_person).unwrap();
497
498     let post_from_blocked_person = PostForm {
499       name: "blocked_person_post".to_string(),
500       creator_id: inserted_blocked_person.id,
501       community_id: inserted_community.id,
502       language_id: Some(LanguageId(1)),
503       ..PostForm::default()
504     };
505
506     Post::create(conn, &post_from_blocked_person).unwrap();
507
508     // block that person
509     let person_block = PersonBlockForm {
510       person_id: inserted_person.id,
511       target_id: inserted_blocked_person.id,
512     };
513
514     PersonBlock::block(conn, &person_block).unwrap();
515
516     // A sample post
517     let new_post = PostForm {
518       name: "test post 3".to_string(),
519       creator_id: inserted_person.id,
520       community_id: inserted_community.id,
521       language_id: Some(LanguageId(47)),
522       ..PostForm::default()
523     };
524
525     let inserted_post = Post::create(conn, &new_post).unwrap();
526
527     let new_bot_post = PostForm {
528       name: "test bot post".to_string(),
529       creator_id: inserted_bot.id,
530       community_id: inserted_community.id,
531       ..PostForm::default()
532     };
533
534     let _inserted_bot_post = Post::create(conn, &new_bot_post).unwrap();
535
536     Data {
537       inserted_person,
538       inserted_local_user,
539       inserted_blocked_person,
540       inserted_bot,
541       inserted_community,
542       inserted_post,
543     }
544   }
545
546   #[test]
547   #[serial]
548   fn post_listing_with_person() {
549     let conn = &mut establish_unpooled_connection();
550     let data = init_data(conn);
551
552     let local_user_form = LocalUserForm {
553       show_bot_accounts: Some(false),
554       ..Default::default()
555     };
556     let inserted_local_user =
557       LocalUser::update(conn, data.inserted_local_user.id, &local_user_form).unwrap();
558
559     let read_post_listing = PostQuery::builder()
560       .conn(conn)
561       .sort(Some(SortType::New))
562       .community_id(Some(data.inserted_community.id))
563       .local_user(Some(&inserted_local_user))
564       .build()
565       .list()
566       .unwrap();
567
568     let post_listing_single_with_person =
569       PostView::read(conn, data.inserted_post.id, Some(data.inserted_person.id)).unwrap();
570
571     let mut expected_post_listing_with_user = expected_post_view(&data, conn);
572
573     // Should be only one person, IE the bot post, and blocked should be missing
574     assert_eq!(1, read_post_listing.len());
575
576     assert_eq!(expected_post_listing_with_user, read_post_listing[0]);
577     expected_post_listing_with_user.my_vote = Some(0);
578     assert_eq!(
579       expected_post_listing_with_user,
580       post_listing_single_with_person
581     );
582
583     let local_user_form = LocalUserForm {
584       show_bot_accounts: Some(true),
585       ..Default::default()
586     };
587     let inserted_local_user =
588       LocalUser::update(conn, data.inserted_local_user.id, &local_user_form).unwrap();
589
590     let post_listings_with_bots = PostQuery::builder()
591       .conn(conn)
592       .sort(Some(SortType::New))
593       .community_id(Some(data.inserted_community.id))
594       .local_user(Some(&inserted_local_user))
595       .build()
596       .list()
597       .unwrap();
598     // should include bot post which has "undetermined" language
599     assert_eq!(2, post_listings_with_bots.len());
600
601     cleanup(data, conn);
602   }
603
604   #[test]
605   #[serial]
606   fn post_listing_no_person() {
607     let conn = &mut establish_unpooled_connection();
608     let data = init_data(conn);
609
610     let read_post_listing_multiple_no_person = PostQuery::builder()
611       .conn(conn)
612       .sort(Some(SortType::New))
613       .community_id(Some(data.inserted_community.id))
614       .build()
615       .list()
616       .unwrap();
617
618     let read_post_listing_single_no_person =
619       PostView::read(conn, data.inserted_post.id, None).unwrap();
620
621     let expected_post_listing_no_person = expected_post_view(&data, conn);
622
623     // Should be 2 posts, with the bot post, and the blocked
624     assert_eq!(3, read_post_listing_multiple_no_person.len());
625
626     assert_eq!(
627       expected_post_listing_no_person,
628       read_post_listing_multiple_no_person[1]
629     );
630     assert_eq!(
631       expected_post_listing_no_person,
632       read_post_listing_single_no_person
633     );
634
635     cleanup(data, conn);
636   }
637
638   #[test]
639   #[serial]
640   fn post_listing_block_community() {
641     let conn = &mut establish_unpooled_connection();
642     let data = init_data(conn);
643
644     let community_block = CommunityBlockForm {
645       person_id: data.inserted_person.id,
646       community_id: data.inserted_community.id,
647     };
648     CommunityBlock::block(conn, &community_block).unwrap();
649
650     let read_post_listings_with_person_after_block = PostQuery::builder()
651       .conn(conn)
652       .sort(Some(SortType::New))
653       .community_id(Some(data.inserted_community.id))
654       .local_user(Some(&data.inserted_local_user))
655       .build()
656       .list()
657       .unwrap();
658     // Should be 0 posts after the community block
659     assert_eq!(0, read_post_listings_with_person_after_block.len());
660
661     CommunityBlock::unblock(conn, &community_block).unwrap();
662     cleanup(data, conn);
663   }
664
665   #[test]
666   #[serial]
667   fn post_listing_like() {
668     let conn = &mut establish_unpooled_connection();
669     let data = init_data(conn);
670
671     let post_like_form = PostLikeForm {
672       post_id: data.inserted_post.id,
673       person_id: data.inserted_person.id,
674       score: 1,
675     };
676
677     let inserted_post_like = PostLike::like(conn, &post_like_form).unwrap();
678
679     let expected_post_like = PostLike {
680       id: inserted_post_like.id,
681       post_id: data.inserted_post.id,
682       person_id: data.inserted_person.id,
683       published: inserted_post_like.published,
684       score: 1,
685     };
686     assert_eq!(expected_post_like, inserted_post_like);
687
688     let like_removed =
689       PostLike::remove(conn, data.inserted_person.id, data.inserted_post.id).unwrap();
690     assert_eq!(1, like_removed);
691     cleanup(data, conn);
692   }
693
694   #[test]
695   #[serial]
696   fn post_listing_person_language() {
697     let conn = &mut establish_unpooled_connection();
698     let data = init_data(conn);
699
700     let spanish_id = Language::read_id_from_code(conn, "es").unwrap();
701     let post_spanish = PostForm {
702       name: "asffgdsc".to_string(),
703       creator_id: data.inserted_person.id,
704       community_id: data.inserted_community.id,
705       language_id: Some(spanish_id),
706       ..PostForm::default()
707     };
708
709     Post::create(conn, &post_spanish).unwrap();
710
711     let post_listings_all = PostQuery::builder()
712       .conn(conn)
713       .sort(Some(SortType::New))
714       .local_user(Some(&data.inserted_local_user))
715       .build()
716       .list()
717       .unwrap();
718
719     // no language filters specified, all posts should be returned
720     assert_eq!(3, post_listings_all.len());
721
722     let french_id = Language::read_id_from_code(conn, "fr").unwrap();
723     LocalUserLanguage::update_user_languages(
724       conn,
725       Some(vec![french_id]),
726       data.inserted_local_user.id,
727     )
728     .unwrap();
729
730     let post_listing_french = PostQuery::builder()
731       .conn(conn)
732       .sort(Some(SortType::New))
733       .local_user(Some(&data.inserted_local_user))
734       .build()
735       .list()
736       .unwrap();
737
738     // only one french language post should be returned
739     assert_eq!(1, post_listing_french.len());
740     assert_eq!(french_id, post_listing_french[0].post.language_id);
741
742     let undetermined_id = Language::read_id_from_code(conn, "und").unwrap();
743     LocalUserLanguage::update_user_languages(
744       conn,
745       Some(vec![french_id, undetermined_id]),
746       data.inserted_local_user.id,
747     )
748     .unwrap();
749     let post_listings_french_und = PostQuery::builder()
750       .conn(conn)
751       .sort(Some(SortType::New))
752       .local_user(Some(&data.inserted_local_user))
753       .build()
754       .list()
755       .unwrap();
756
757     // french post and undetermined language post should be returned
758     assert_eq!(2, post_listings_french_und.len());
759     assert_eq!(
760       undetermined_id,
761       post_listings_french_und[0].post.language_id
762     );
763     assert_eq!(french_id, post_listings_french_und[1].post.language_id);
764
765     cleanup(data, conn);
766   }
767
768   fn cleanup(data: Data, conn: &mut PgConnection) {
769     let num_deleted = Post::delete(conn, data.inserted_post.id).unwrap();
770     Community::delete(conn, data.inserted_community.id).unwrap();
771     Person::delete(conn, data.inserted_person.id).unwrap();
772     Person::delete(conn, data.inserted_bot.id).unwrap();
773     Person::delete(conn, data.inserted_blocked_person.id).unwrap();
774     assert_eq!(1, num_deleted);
775   }
776
777   fn expected_post_view(data: &Data, conn: &mut PgConnection) -> PostView {
778     let (inserted_person, inserted_community, inserted_post) = (
779       &data.inserted_person,
780       &data.inserted_community,
781       &data.inserted_post,
782     );
783     let agg = PostAggregates::read(conn, inserted_post.id).unwrap();
784
785     PostView {
786       post: Post {
787         id: inserted_post.id,
788         name: inserted_post.name.clone(),
789         creator_id: inserted_person.id,
790         url: None,
791         body: None,
792         published: inserted_post.published,
793         updated: None,
794         community_id: inserted_community.id,
795         removed: false,
796         deleted: false,
797         locked: false,
798         stickied: false,
799         nsfw: false,
800         embed_title: None,
801         embed_description: None,
802         embed_video_url: None,
803         thumbnail_url: None,
804         ap_id: inserted_post.ap_id.to_owned(),
805         local: true,
806         language_id: LanguageId(47),
807       },
808       my_vote: None,
809       creator: PersonSafe {
810         id: inserted_person.id,
811         name: inserted_person.name.clone(),
812         display_name: None,
813         published: inserted_person.published,
814         avatar: None,
815         actor_id: inserted_person.actor_id.to_owned(),
816         local: true,
817         admin: false,
818         bot_account: false,
819         banned: false,
820         deleted: false,
821         bio: None,
822         banner: None,
823         updated: None,
824         inbox_url: inserted_person.inbox_url.to_owned(),
825         shared_inbox_url: None,
826         matrix_user_id: None,
827         ban_expires: None,
828       },
829       creator_banned_from_community: false,
830       community: CommunitySafe {
831         id: inserted_community.id,
832         name: inserted_community.name.clone(),
833         icon: None,
834         removed: false,
835         deleted: false,
836         nsfw: false,
837         actor_id: inserted_community.actor_id.to_owned(),
838         local: true,
839         title: "nada".to_owned(),
840         description: None,
841         updated: None,
842         banner: None,
843         hidden: false,
844         posting_restricted_to_mods: false,
845         published: inserted_community.published,
846       },
847       counts: PostAggregates {
848         id: agg.id,
849         post_id: inserted_post.id,
850         comments: 0,
851         score: 0,
852         upvotes: 0,
853         downvotes: 0,
854         stickied: false,
855         published: agg.published,
856         newest_comment_time_necro: inserted_post.published,
857         newest_comment_time: inserted_post.published,
858       },
859       subscribed: SubscribedType::NotSubscribed,
860       read: false,
861       saved: false,
862       creator_blocked: false,
863     }
864   }
865 }