]> Untitled Git - lemmy.git/blob - crates/db_views/src/comment_view.rs
add test for comment view languages
[lemmy.git] / crates / db_views / src / comment_view.rs
1 use crate::structs::CommentView;
2 use diesel::{dsl::*, result::Error, *};
3 use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions};
4 use lemmy_db_schema::{
5   aggregates::structs::CommentAggregates,
6   newtypes::{CommentId, CommunityId, DbUrl, LocalUserId, PersonId, PostId},
7   schema::{
8     comment,
9     comment_aggregates,
10     comment_like,
11     comment_saved,
12     community,
13     community_block,
14     community_follower,
15     community_person_ban,
16     language,
17     local_user_language,
18     person,
19     person_block,
20     post,
21   },
22   source::{
23     comment::{Comment, CommentSaved},
24     community::{Community, CommunityFollower, CommunityPersonBan, CommunitySafe},
25     local_user::LocalUser,
26     person::{Person, PersonSafe},
27     person_block::PersonBlock,
28     post::Post,
29   },
30   traits::{ToSafe, ViewToVec},
31   utils::{functions::hot_rank, fuzzy_search, limit_and_offset_unlimited},
32   CommentSortType,
33   ListingType,
34 };
35 use typed_builder::TypedBuilder;
36
37 type CommentViewTuple = (
38   Comment,
39   PersonSafe,
40   Post,
41   CommunitySafe,
42   CommentAggregates,
43   Option<CommunityPersonBan>,
44   Option<CommunityFollower>,
45   Option<CommentSaved>,
46   Option<PersonBlock>,
47   Option<i16>,
48 );
49
50 impl CommentView {
51   pub fn read(
52     conn: &PgConnection,
53     comment_id: CommentId,
54     my_person_id: Option<PersonId>,
55   ) -> Result<Self, Error> {
56     // The left join below will return None in this case
57     let person_id_join = my_person_id.unwrap_or(PersonId(-1));
58
59     let (
60       comment,
61       creator,
62       post,
63       community,
64       counts,
65       creator_banned_from_community,
66       follower,
67       saved,
68       creator_blocked,
69       comment_like,
70     ) = comment::table
71       .find(comment_id)
72       .inner_join(person::table)
73       .inner_join(post::table)
74       .inner_join(community::table.on(post::community_id.eq(community::id)))
75       .inner_join(comment_aggregates::table)
76       .left_join(
77         community_person_ban::table.on(
78           community::id
79             .eq(community_person_ban::community_id)
80             .and(community_person_ban::person_id.eq(comment::creator_id))
81             .and(
82               community_person_ban::expires
83                 .is_null()
84                 .or(community_person_ban::expires.gt(now)),
85             ),
86         ),
87       )
88       .left_join(
89         community_follower::table.on(
90           post::community_id
91             .eq(community_follower::community_id)
92             .and(community_follower::person_id.eq(person_id_join)),
93         ),
94       )
95       .left_join(
96         comment_saved::table.on(
97           comment::id
98             .eq(comment_saved::comment_id)
99             .and(comment_saved::person_id.eq(person_id_join)),
100         ),
101       )
102       .left_join(
103         person_block::table.on(
104           comment::creator_id
105             .eq(person_block::target_id)
106             .and(person_block::person_id.eq(person_id_join)),
107         ),
108       )
109       .left_join(
110         comment_like::table.on(
111           comment::id
112             .eq(comment_like::comment_id)
113             .and(comment_like::person_id.eq(person_id_join)),
114         ),
115       )
116       .select((
117         comment::all_columns,
118         Person::safe_columns_tuple(),
119         post::all_columns,
120         Community::safe_columns_tuple(),
121         comment_aggregates::all_columns,
122         community_person_ban::all_columns.nullable(),
123         community_follower::all_columns.nullable(),
124         comment_saved::all_columns.nullable(),
125         person_block::all_columns.nullable(),
126         comment_like::score.nullable(),
127       ))
128       .first::<CommentViewTuple>(conn)?;
129
130     // If a person is given, then my_vote, if None, should be 0, not null
131     // Necessary to differentiate between other person's votes
132     let my_vote = if my_person_id.is_some() && comment_like.is_none() {
133       Some(0)
134     } else {
135       comment_like
136     };
137
138     Ok(CommentView {
139       comment,
140       post,
141       creator,
142       community,
143       counts,
144       creator_banned_from_community: creator_banned_from_community.is_some(),
145       subscribed: CommunityFollower::to_subscribed_type(&follower),
146       saved: saved.is_some(),
147       creator_blocked: creator_blocked.is_some(),
148       my_vote,
149     })
150   }
151 }
152
153 #[derive(TypedBuilder)]
154 #[builder(field_defaults(default))]
155 pub struct CommentQuery<'a> {
156   #[builder(!default)]
157   conn: &'a PgConnection,
158   listing_type: Option<ListingType>,
159   sort: Option<CommentSortType>,
160   community_id: Option<CommunityId>,
161   community_actor_id: Option<DbUrl>,
162   post_id: Option<PostId>,
163   parent_path: Option<Ltree>,
164   creator_id: Option<PersonId>,
165   local_user: Option<&'a LocalUser>,
166   search_term: Option<String>,
167   saved_only: Option<bool>,
168   page: Option<i64>,
169   limit: Option<i64>,
170   max_depth: Option<i32>,
171 }
172
173 impl<'a> CommentQuery<'a> {
174   pub fn list(self) -> Result<Vec<CommentView>, Error> {
175     use diesel::dsl::*;
176
177     // The left join below will return None in this case
178     let person_id_join = self.local_user.map(|l| l.person_id).unwrap_or(PersonId(-1));
179     let local_user_id_join = self.local_user.map(|l| l.id).unwrap_or(LocalUserId(-1));
180
181     let mut query = comment::table
182       .inner_join(person::table)
183       .inner_join(post::table)
184       .inner_join(community::table.on(post::community_id.eq(community::id)))
185       .inner_join(comment_aggregates::table)
186       .left_join(
187         community_person_ban::table.on(
188           community::id
189             .eq(community_person_ban::community_id)
190             .and(community_person_ban::person_id.eq(comment::creator_id))
191             .and(
192               community_person_ban::expires
193                 .is_null()
194                 .or(community_person_ban::expires.gt(now)),
195             ),
196         ),
197       )
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         comment_saved::table.on(
207           comment::id
208             .eq(comment_saved::comment_id)
209             .and(comment_saved::person_id.eq(person_id_join)),
210         ),
211       )
212       .left_join(
213         person_block::table.on(
214           comment::creator_id
215             .eq(person_block::target_id)
216             .and(person_block::person_id.eq(person_id_join)),
217         ),
218       )
219       .left_join(
220         community_block::table.on(
221           community::id
222             .eq(community_block::community_id)
223             .and(community_block::person_id.eq(person_id_join)),
224         ),
225       )
226       .left_join(
227         comment_like::table.on(
228           comment::id
229             .eq(comment_like::comment_id)
230             .and(comment_like::person_id.eq(person_id_join)),
231         ),
232       )
233       .inner_join(language::table)
234       .left_join(
235         local_user_language::table.on(
236           comment::language_id
237             .eq(local_user_language::language_id)
238             .and(local_user_language::local_user_id.eq(local_user_id_join)),
239         ),
240       )
241       .select((
242         comment::all_columns,
243         Person::safe_columns_tuple(),
244         post::all_columns,
245         Community::safe_columns_tuple(),
246         comment_aggregates::all_columns,
247         community_person_ban::all_columns.nullable(),
248         community_follower::all_columns.nullable(),
249         comment_saved::all_columns.nullable(),
250         person_block::all_columns.nullable(),
251         comment_like::score.nullable(),
252       ))
253       .into_boxed();
254
255     if let Some(creator_id) = self.creator_id {
256       query = query.filter(comment::creator_id.eq(creator_id));
257     };
258
259     if let Some(post_id) = self.post_id {
260       query = query.filter(comment::post_id.eq(post_id));
261     };
262
263     if let Some(parent_path) = self.parent_path.as_ref() {
264       query = query.filter(comment::path.contained_by(parent_path));
265     };
266
267     if let Some(search_term) = self.search_term {
268       query = query.filter(comment::content.ilike(fuzzy_search(&search_term)));
269     };
270
271     if let Some(listing_type) = self.listing_type {
272       match listing_type {
273         ListingType::Subscribed => {
274           query = query.filter(community_follower::person_id.is_not_null())
275         } // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
276         ListingType::Local => {
277           query = query.filter(community::local.eq(true)).filter(
278             community::hidden
279               .eq(false)
280               .or(community_follower::person_id.eq(person_id_join)),
281           )
282         }
283         ListingType::All => {
284           query = query.filter(
285             community::hidden
286               .eq(false)
287               .or(community_follower::person_id.eq(person_id_join)),
288           )
289         }
290       }
291     };
292
293     if let Some(community_id) = self.community_id {
294       query = query.filter(post::community_id.eq(community_id));
295     }
296
297     if let Some(community_actor_id) = self.community_actor_id {
298       query = query.filter(community::actor_id.eq(community_actor_id))
299     }
300
301     if self.saved_only.unwrap_or(false) {
302       query = query.filter(comment_saved::id.is_not_null());
303     }
304
305     if !self.local_user.map(|l| l.show_bot_accounts).unwrap_or(true) {
306       query = query.filter(person::bot_account.eq(false));
307     };
308
309     if self.local_user.is_some() {
310       // Filter out the rows with missing languages
311       query = query.filter(local_user_language::id.is_not_null());
312
313       // Don't show blocked communities or persons
314       query = query.filter(community_block::person_id.is_null());
315       query = query.filter(person_block::person_id.is_null());
316     }
317
318     // A Max depth given means its a tree fetch
319     let (limit, offset) = if let Some(max_depth) = self.max_depth {
320       let depth_limit = if let Some(parent_path) = self.parent_path.as_ref() {
321         parent_path.0.split('.').count() as i32 + max_depth
322         // Add one because of root "0"
323       } else {
324         max_depth + 1
325       };
326
327       query = query.filter(nlevel(comment::path).le(depth_limit));
328
329       // Always order by the parent path first
330       query = query.order_by(subpath(comment::path, 0, -1));
331
332       // TODO limit question. Limiting does not work for comment threads ATM, only max_depth
333       // For now, don't do any limiting for tree fetches
334       // https://stackoverflow.com/questions/72983614/postgres-ltree-how-to-limit-the-max-number-of-children-at-any-given-level
335
336       // Don't use the regular error-checking one, many more comments must ofter be fetched.
337       // This does not work for comment trees, and the limit should be manually set to a high number
338       //
339       // If a max depth is given, then you know its a tree fetch, and limits should be ignored
340       (i64::MAX, 0)
341     } else {
342       limit_and_offset_unlimited(self.page, self.limit)
343     };
344
345     query = match self.sort.unwrap_or(CommentSortType::Hot) {
346       CommentSortType::Hot => query
347         .then_order_by(hot_rank(comment_aggregates::score, comment_aggregates::published).desc())
348         .then_order_by(comment_aggregates::published.desc()),
349       CommentSortType::New => query.then_order_by(comment::published.desc()),
350       CommentSortType::Old => query.then_order_by(comment::published.asc()),
351       CommentSortType::Top => query.order_by(comment_aggregates::score.desc()),
352     };
353
354     // Note: deleted and removed comments are done on the front side
355     let res = query
356       .limit(limit)
357       .offset(offset)
358       .load::<CommentViewTuple>(self.conn)?;
359
360     Ok(CommentView::from_tuple_to_vec(res))
361   }
362 }
363
364 impl ViewToVec for CommentView {
365   type DbTuple = CommentViewTuple;
366   fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
367     items
368       .into_iter()
369       .map(|a| Self {
370         comment: a.0,
371         creator: a.1,
372         post: a.2,
373         community: a.3,
374         counts: a.4,
375         creator_banned_from_community: a.5.is_some(),
376         subscribed: CommunityFollower::to_subscribed_type(&a.6),
377         saved: a.7.is_some(),
378         creator_blocked: a.8.is_some(),
379         my_vote: a.9,
380       })
381       .collect::<Vec<Self>>()
382   }
383 }
384
385 #[cfg(test)]
386 mod tests {
387   use crate::comment_view::*;
388   use lemmy_db_schema::{
389     aggregates::structs::CommentAggregates,
390     newtypes::LanguageId,
391     source::{
392       comment::*,
393       community::*,
394       language::Language,
395       local_user::LocalUserForm,
396       local_user_language::LocalUserLanguage,
397       person::*,
398       person_block::PersonBlockForm,
399       post::*,
400     },
401     traits::{Blockable, Crud, Likeable},
402     utils::establish_unpooled_connection,
403     SubscribedType,
404   };
405   use serial_test::serial;
406
407   struct Data {
408     inserted_comment_0: Comment,
409     inserted_comment_1: Comment,
410     inserted_comment_2: Comment,
411     inserted_post: Post,
412     inserted_person: Person,
413     inserted_local_user: LocalUser,
414     inserted_person_2: Person,
415     inserted_community: Community,
416   }
417
418   fn init_data(conn: &PgConnection) -> Data {
419     let new_person = PersonForm {
420       name: "timmy".into(),
421       public_key: Some("pubkey".to_string()),
422       ..PersonForm::default()
423     };
424     let inserted_person = Person::create(&conn, &new_person).unwrap();
425     let local_user_form = LocalUserForm {
426       person_id: Some(inserted_person.id),
427       password_encrypted: Some("".to_string()),
428       ..Default::default()
429     };
430     let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap();
431
432     let new_person_2 = PersonForm {
433       name: "sara".into(),
434       public_key: Some("pubkey".to_string()),
435       ..PersonForm::default()
436     };
437     let inserted_person_2 = Person::create(&conn, &new_person_2).unwrap();
438
439     let new_community = CommunityForm {
440       name: "test community 5".to_string(),
441       title: "nada".to_owned(),
442       public_key: Some("pubkey".to_string()),
443       ..CommunityForm::default()
444     };
445
446     let inserted_community = Community::create(&conn, &new_community).unwrap();
447
448     let new_post = PostForm {
449       name: "A test post 2".into(),
450       creator_id: inserted_person.id,
451       community_id: inserted_community.id,
452       ..PostForm::default()
453     };
454
455     let inserted_post = Post::create(&conn, &new_post).unwrap();
456
457     // Create a comment tree with this hierarchy
458     //       0
459     //     \     \
460     //    1      2
461     //    \
462     //  3  4
463     //     \
464     //     5
465     let comment_form_0 = CommentForm {
466       content: "Comment 0".into(),
467       creator_id: inserted_person.id,
468       post_id: inserted_post.id,
469       ..CommentForm::default()
470     };
471
472     let inserted_comment_0 = Comment::create(&conn, &comment_form_0, None).unwrap();
473
474     let comment_form_1 = CommentForm {
475       content: "Comment 1, A test blocked comment".into(),
476       creator_id: inserted_person_2.id,
477       post_id: inserted_post.id,
478       ..CommentForm::default()
479     };
480
481     let inserted_comment_1 =
482       Comment::create(&conn, &comment_form_1, Some(&inserted_comment_0.path)).unwrap();
483
484     let finnish_id = Language::read_id_from_code(&conn, "fi").unwrap();
485     let comment_form_2 = CommentForm {
486       content: "Comment 2".into(),
487       creator_id: inserted_person.id,
488       post_id: inserted_post.id,
489       language_id: Some(finnish_id),
490       ..CommentForm::default()
491     };
492
493     let inserted_comment_2 =
494       Comment::create(&conn, &comment_form_2, Some(&inserted_comment_0.path)).unwrap();
495
496     let comment_form_3 = CommentForm {
497       content: "Comment 3".into(),
498       creator_id: inserted_person.id,
499       post_id: inserted_post.id,
500       ..CommentForm::default()
501     };
502
503     let _inserted_comment_3 =
504       Comment::create(&conn, &comment_form_3, Some(&inserted_comment_1.path)).unwrap();
505
506     let polish_id = Language::read_id_from_code(&conn, "pl").unwrap();
507     let comment_form_4 = CommentForm {
508       content: "Comment 4".into(),
509       creator_id: inserted_person.id,
510       post_id: inserted_post.id,
511       language_id: Some(polish_id),
512       ..CommentForm::default()
513     };
514
515     let inserted_comment_4 =
516       Comment::create(&conn, &comment_form_4, Some(&inserted_comment_1.path)).unwrap();
517
518     let comment_form_5 = CommentForm {
519       content: "Comment 5".into(),
520       creator_id: inserted_person.id,
521       post_id: inserted_post.id,
522       ..CommentForm::default()
523     };
524
525     let _inserted_comment_5 =
526       Comment::create(&conn, &comment_form_5, Some(&inserted_comment_4.path)).unwrap();
527
528     let timmy_blocks_sara_form = PersonBlockForm {
529       person_id: inserted_person.id,
530       target_id: inserted_person_2.id,
531     };
532
533     let inserted_block = PersonBlock::block(&conn, &timmy_blocks_sara_form).unwrap();
534
535     let expected_block = PersonBlock {
536       id: inserted_block.id,
537       person_id: inserted_person.id,
538       target_id: inserted_person_2.id,
539       published: inserted_block.published,
540     };
541     assert_eq!(expected_block, inserted_block);
542
543     let comment_like_form = CommentLikeForm {
544       comment_id: inserted_comment_0.id,
545       post_id: inserted_post.id,
546       person_id: inserted_person.id,
547       score: 1,
548     };
549
550     let _inserted_comment_like = CommentLike::like(&conn, &comment_like_form).unwrap();
551
552     Data {
553       inserted_comment_0,
554       inserted_comment_1,
555       inserted_comment_2,
556       inserted_post,
557       inserted_person,
558       inserted_local_user,
559       inserted_person_2,
560       inserted_community,
561     }
562   }
563
564   #[test]
565   #[serial]
566   fn test_crud() {
567     let conn = establish_unpooled_connection();
568     let data = init_data(&conn);
569
570     let expected_comment_view_no_person = expected_comment_view(&data, &conn);
571
572     let mut expected_comment_view_with_person = expected_comment_view_no_person.to_owned();
573     expected_comment_view_with_person.my_vote = Some(1);
574
575     let read_comment_views_no_person = CommentQuery::builder()
576       .conn(&conn)
577       .post_id(Some(data.inserted_post.id))
578       .build()
579       .list()
580       .unwrap();
581
582     assert_eq!(
583       expected_comment_view_no_person,
584       read_comment_views_no_person[0]
585     );
586
587     let read_comment_views_with_person = CommentQuery::builder()
588       .conn(&conn)
589       .post_id(Some(data.inserted_post.id))
590       .local_user(Some(&data.inserted_local_user))
591       .build()
592       .list()
593       .unwrap();
594
595     assert_eq!(
596       expected_comment_view_with_person,
597       read_comment_views_with_person[0]
598     );
599
600     // Make sure its 1, not showing the blocked comment
601     assert_eq!(5, read_comment_views_with_person.len());
602
603     let read_comment_from_blocked_person = CommentView::read(
604       &conn,
605       data.inserted_comment_1.id,
606       Some(data.inserted_person.id),
607     )
608     .unwrap();
609
610     // Make sure block set the creator blocked
611     assert!(read_comment_from_blocked_person.creator_blocked);
612
613     cleanup(data, &conn);
614   }
615
616   #[test]
617   #[serial]
618   fn test_comment_tree() {
619     let conn = establish_unpooled_connection();
620     let data = init_data(&conn);
621
622     let top_path = data.inserted_comment_0.path.clone();
623     let read_comment_views_top_path = CommentQuery::builder()
624       .conn(&conn)
625       .post_id(Some(data.inserted_post.id))
626       .parent_path(Some(top_path))
627       .build()
628       .list()
629       .unwrap();
630
631     let child_path = data.inserted_comment_1.path.clone();
632     let read_comment_views_child_path = CommentQuery::builder()
633       .conn(&conn)
634       .post_id(Some(data.inserted_post.id))
635       .parent_path(Some(child_path))
636       .build()
637       .list()
638       .unwrap();
639
640     // Make sure the comment parent-limited fetch is correct
641     assert_eq!(6, read_comment_views_top_path.len());
642     assert_eq!(4, read_comment_views_child_path.len());
643
644     // Make sure it contains the parent, but not the comment from the other tree
645     let child_comments = read_comment_views_child_path
646       .into_iter()
647       .map(|c| c.comment)
648       .collect::<Vec<Comment>>();
649     assert!(child_comments.contains(&data.inserted_comment_1));
650     assert!(!child_comments.contains(&data.inserted_comment_2));
651
652     let read_comment_views_top_max_depth = CommentQuery::builder()
653       .conn(&conn)
654       .post_id(Some(data.inserted_post.id))
655       .max_depth(Some(1))
656       .build()
657       .list()
658       .unwrap();
659
660     // Make sure a depth limited one only has the top comment
661     assert_eq!(
662       expected_comment_view(&data, &conn),
663       read_comment_views_top_max_depth[0]
664     );
665     assert_eq!(1, read_comment_views_top_max_depth.len());
666
667     let child_path = data.inserted_comment_1.path.clone();
668     let read_comment_views_parent_max_depth = CommentQuery::builder()
669       .conn(&conn)
670       .post_id(Some(data.inserted_post.id))
671       .parent_path(Some(child_path))
672       .max_depth(Some(1))
673       .sort(Some(CommentSortType::New))
674       .build()
675       .list()
676       .unwrap();
677
678     // Make sure a depth limited one, and given child comment 1, has 3
679     assert!(read_comment_views_parent_max_depth[2]
680       .comment
681       .content
682       .eq("Comment 3"));
683     assert_eq!(3, read_comment_views_parent_max_depth.len());
684
685     cleanup(data, &conn);
686   }
687
688   #[test]
689   #[serial]
690   fn test_languages() {
691     let conn = establish_unpooled_connection();
692     let data = init_data(&conn);
693
694     // by default, user has all languages enabled and should see all comments
695     // (except from blocked user)
696     let all_languages = CommentQuery::builder()
697       .conn(&conn)
698       .local_user(Some(&data.inserted_local_user))
699       .build()
700       .list()
701       .unwrap();
702     assert_eq!(5, all_languages.len());
703
704     // change user lang to finnish, should only show single finnish comment
705     let finnish_id = Language::read_id_from_code(&conn, "fi").unwrap();
706     LocalUserLanguage::update_user_languages(
707       &conn,
708       Some(vec![finnish_id]),
709       data.inserted_local_user.id,
710     )
711     .unwrap();
712     let finnish_comment = CommentQuery::builder()
713       .conn(&conn)
714       .local_user(Some(&data.inserted_local_user))
715       .build()
716       .list()
717       .unwrap();
718     assert_eq!(1, finnish_comment.len());
719     assert_eq!(
720       data.inserted_comment_2.content,
721       finnish_comment[0].comment.content
722     );
723     assert_eq!(finnish_id, finnish_comment[0].comment.language_id);
724
725     // now show all comments with undetermined language (which is the default value)
726     let undetermined_id = Language::read_id_from_code(&conn, "und").unwrap();
727     LocalUserLanguage::update_user_languages(
728       &conn,
729       Some(vec![undetermined_id]),
730       data.inserted_local_user.id,
731     )
732     .unwrap();
733     let undetermined_comment = CommentQuery::builder()
734       .conn(&conn)
735       .local_user(Some(&data.inserted_local_user))
736       .build()
737       .list()
738       .unwrap();
739     assert_eq!(3, undetermined_comment.len());
740
741     cleanup(data, &conn);
742   }
743
744   fn cleanup(data: Data, conn: &PgConnection) {
745     CommentLike::remove(&conn, data.inserted_person.id, data.inserted_comment_0.id).unwrap();
746     Comment::delete(&conn, data.inserted_comment_0.id).unwrap();
747     Comment::delete(&conn, data.inserted_comment_1.id).unwrap();
748     Post::delete(&conn, data.inserted_post.id).unwrap();
749     Community::delete(&conn, data.inserted_community.id).unwrap();
750     Person::delete(&conn, data.inserted_person.id).unwrap();
751     Person::delete(&conn, data.inserted_person_2.id).unwrap();
752   }
753
754   fn expected_comment_view(data: &Data, conn: &PgConnection) -> CommentView {
755     let agg = CommentAggregates::read(&conn, data.inserted_comment_0.id).unwrap();
756     CommentView {
757       creator_banned_from_community: false,
758       my_vote: None,
759       subscribed: SubscribedType::NotSubscribed,
760       saved: false,
761       creator_blocked: false,
762       comment: Comment {
763         id: data.inserted_comment_0.id,
764         content: "Comment 0".into(),
765         creator_id: data.inserted_person.id,
766         post_id: data.inserted_post.id,
767         removed: false,
768         deleted: false,
769         published: data.inserted_comment_0.published,
770         ap_id: data.inserted_comment_0.ap_id.clone(),
771         updated: None,
772         local: true,
773         distinguished: false,
774         path: data.inserted_comment_0.to_owned().path,
775         language_id: LanguageId(0),
776       },
777       creator: PersonSafe {
778         id: data.inserted_person.id,
779         name: "timmy".into(),
780         display_name: None,
781         published: data.inserted_person.published,
782         avatar: None,
783         actor_id: data.inserted_person.actor_id.to_owned(),
784         local: true,
785         banned: false,
786         deleted: false,
787         admin: false,
788         bot_account: false,
789         bio: None,
790         banner: None,
791         updated: None,
792         inbox_url: data.inserted_person.inbox_url.to_owned(),
793         shared_inbox_url: None,
794         matrix_user_id: None,
795         ban_expires: None,
796       },
797       post: Post {
798         id: data.inserted_post.id,
799         name: data.inserted_post.name.to_owned(),
800         creator_id: data.inserted_person.id,
801         url: None,
802         body: None,
803         published: data.inserted_post.published,
804         updated: None,
805         community_id: data.inserted_community.id,
806         removed: false,
807         deleted: false,
808         locked: false,
809         stickied: false,
810         nsfw: false,
811         embed_title: None,
812         embed_description: None,
813         embed_video_url: None,
814         thumbnail_url: None,
815         ap_id: data.inserted_post.ap_id.to_owned(),
816         local: true,
817         language_id: Default::default(),
818       },
819       community: CommunitySafe {
820         id: data.inserted_community.id,
821         name: "test community 5".to_string(),
822         icon: None,
823         removed: false,
824         deleted: false,
825         nsfw: false,
826         actor_id: data.inserted_community.actor_id.to_owned(),
827         local: true,
828         title: "nada".to_owned(),
829         description: None,
830         updated: None,
831         banner: None,
832         hidden: false,
833         posting_restricted_to_mods: false,
834         published: data.inserted_community.published,
835       },
836       counts: CommentAggregates {
837         id: agg.id,
838         comment_id: data.inserted_comment_0.id,
839         score: 1,
840         upvotes: 1,
841         downvotes: 0,
842         published: agg.published,
843         child_count: 5,
844       },
845     }
846   }
847 }