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