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