]> Untitled Git - lemmy.git/blob - crates/db_views/src/comment_view.rs
5a94d37a52d0d959711e2ff209f7b374ff59673b
[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       instance::Instance,
400       language::Language,
401       local_user::LocalUserInsertForm,
402       person::*,
403       person_block::PersonBlockForm,
404       post::*,
405     },
406     traits::{Blockable, Crud, Likeable},
407     utils::establish_unpooled_connection,
408     SubscribedType,
409   };
410   use serial_test::serial;
411
412   struct Data {
413     inserted_instance: Instance,
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: &mut PgConnection) -> Data {
425     let inserted_instance = Instance::create(conn, "my_domain.tld").unwrap();
426
427     let new_person = PersonInsertForm::builder()
428       .name("timmy".into())
429       .public_key("pubkey".to_string())
430       .instance_id(inserted_instance.id)
431       .build();
432     let inserted_person = Person::create(conn, &new_person).unwrap();
433     let local_user_form = LocalUserInsertForm::builder()
434       .person_id(inserted_person.id)
435       .password_encrypted("".to_string())
436       .build();
437     let inserted_local_user = LocalUser::create(conn, &local_user_form).unwrap();
438
439     let new_person_2 = PersonInsertForm::builder()
440       .name("sara".into())
441       .public_key("pubkey".to_string())
442       .instance_id(inserted_instance.id)
443       .build();
444     let inserted_person_2 = Person::create(conn, &new_person_2).unwrap();
445
446     let new_community = CommunityInsertForm::builder()
447       .name("test community 5".to_string())
448       .title("nada".to_owned())
449       .public_key("pubkey".to_string())
450       .instance_id(inserted_instance.id)
451       .build();
452
453     let inserted_community = Community::create(conn, &new_community).unwrap();
454
455     let new_post = PostInsertForm::builder()
456       .name("A test post 2".into())
457       .creator_id(inserted_person.id)
458       .community_id(inserted_community.id)
459       .build();
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 = CommentInsertForm::builder()
472       .content("Comment 0".into())
473       .creator_id(inserted_person.id)
474       .post_id(inserted_post.id)
475       .build();
476
477     let inserted_comment_0 = Comment::create(conn, &comment_form_0, None).unwrap();
478
479     let comment_form_1 = CommentInsertForm::builder()
480       .content("Comment 1, A test blocked comment".into())
481       .creator_id(inserted_person_2.id)
482       .post_id(inserted_post.id)
483       .build();
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 = CommentInsertForm::builder()
490       .content("Comment 2".into())
491       .creator_id(inserted_person.id)
492       .post_id(inserted_post.id)
493       .language_id(Some(finnish_id))
494       .build();
495
496     let inserted_comment_2 =
497       Comment::create(conn, &comment_form_2, Some(&inserted_comment_0.path)).unwrap();
498
499     let comment_form_3 = CommentInsertForm::builder()
500       .content("Comment 3".into())
501       .creator_id(inserted_person.id)
502       .post_id(inserted_post.id)
503       .build();
504
505     let _inserted_comment_3 =
506       Comment::create(conn, &comment_form_3, Some(&inserted_comment_1.path)).unwrap();
507
508     let polish_id = Language::read_id_from_code(conn, "pl").unwrap();
509     let comment_form_4 = CommentInsertForm::builder()
510       .content("Comment 4".into())
511       .creator_id(inserted_person.id)
512       .post_id(inserted_post.id)
513       .language_id(Some(polish_id))
514       .build();
515
516     let inserted_comment_4 =
517       Comment::create(conn, &comment_form_4, Some(&inserted_comment_1.path)).unwrap();
518
519     let comment_form_5 = CommentInsertForm::builder()
520       .content("Comment 5".into())
521       .creator_id(inserted_person.id)
522       .post_id(inserted_post.id)
523       .build();
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_instance,
554       inserted_comment_0,
555       inserted_comment_1,
556       inserted_comment_2,
557       inserted_post,
558       inserted_person,
559       inserted_local_user,
560       inserted_person_2,
561       inserted_community,
562     }
563   }
564
565   #[test]
566   #[serial]
567   fn test_crud() {
568     let conn = &mut establish_unpooled_connection();
569     let data = init_data(conn);
570
571     let expected_comment_view_no_person = expected_comment_view(&data, conn);
572
573     let mut expected_comment_view_with_person = expected_comment_view_no_person.to_owned();
574     expected_comment_view_with_person.my_vote = Some(1);
575
576     let read_comment_views_no_person = CommentQuery::builder()
577       .conn(conn)
578       .post_id(Some(data.inserted_post.id))
579       .build()
580       .list()
581       .unwrap();
582
583     assert_eq!(
584       expected_comment_view_no_person,
585       read_comment_views_no_person[0]
586     );
587
588     let read_comment_views_with_person = CommentQuery::builder()
589       .conn(conn)
590       .post_id(Some(data.inserted_post.id))
591       .local_user(Some(&data.inserted_local_user))
592       .build()
593       .list()
594       .unwrap();
595
596     assert_eq!(
597       expected_comment_view_with_person,
598       read_comment_views_with_person[0]
599     );
600
601     // Make sure its 1, not showing the blocked comment
602     assert_eq!(5, read_comment_views_with_person.len());
603
604     let read_comment_from_blocked_person = CommentView::read(
605       conn,
606       data.inserted_comment_1.id,
607       Some(data.inserted_person.id),
608     )
609     .unwrap();
610
611     // Make sure block set the creator blocked
612     assert!(read_comment_from_blocked_person.creator_blocked);
613
614     cleanup(data, conn);
615   }
616
617   #[test]
618   #[serial]
619   fn test_comment_tree() {
620     let conn = &mut establish_unpooled_connection();
621     let data = init_data(conn);
622
623     let top_path = data.inserted_comment_0.path.clone();
624     let read_comment_views_top_path = CommentQuery::builder()
625       .conn(conn)
626       .post_id(Some(data.inserted_post.id))
627       .parent_path(Some(top_path))
628       .build()
629       .list()
630       .unwrap();
631
632     let child_path = data.inserted_comment_1.path.clone();
633     let read_comment_views_child_path = CommentQuery::builder()
634       .conn(conn)
635       .post_id(Some(data.inserted_post.id))
636       .parent_path(Some(child_path))
637       .build()
638       .list()
639       .unwrap();
640
641     // Make sure the comment parent-limited fetch is correct
642     assert_eq!(6, read_comment_views_top_path.len());
643     assert_eq!(4, read_comment_views_child_path.len());
644
645     // Make sure it contains the parent, but not the comment from the other tree
646     let child_comments = read_comment_views_child_path
647       .into_iter()
648       .map(|c| c.comment)
649       .collect::<Vec<Comment>>();
650     assert!(child_comments.contains(&data.inserted_comment_1));
651     assert!(!child_comments.contains(&data.inserted_comment_2));
652
653     let read_comment_views_top_max_depth = CommentQuery::builder()
654       .conn(conn)
655       .post_id(Some(data.inserted_post.id))
656       .max_depth(Some(1))
657       .build()
658       .list()
659       .unwrap();
660
661     // Make sure a depth limited one only has the top comment
662     assert_eq!(
663       expected_comment_view(&data, conn),
664       read_comment_views_top_max_depth[0]
665     );
666     assert_eq!(1, read_comment_views_top_max_depth.len());
667
668     let child_path = data.inserted_comment_1.path.clone();
669     let read_comment_views_parent_max_depth = CommentQuery::builder()
670       .conn(conn)
671       .post_id(Some(data.inserted_post.id))
672       .parent_path(Some(child_path))
673       .max_depth(Some(1))
674       .sort(Some(CommentSortType::New))
675       .build()
676       .list()
677       .unwrap();
678
679     // Make sure a depth limited one, and given child comment 1, has 3
680     assert!(read_comment_views_parent_max_depth[2]
681       .comment
682       .content
683       .eq("Comment 3"));
684     assert_eq!(3, read_comment_views_parent_max_depth.len());
685
686     cleanup(data, conn);
687   }
688
689   #[test]
690   #[serial]
691   fn test_languages() {
692     let conn = &mut establish_unpooled_connection();
693     let data = init_data(conn);
694
695     // by default, user has all languages enabled and should see all comments
696     // (except from blocked user)
697     let all_languages = CommentQuery::builder()
698       .conn(conn)
699       .local_user(Some(&data.inserted_local_user))
700       .build()
701       .list()
702       .unwrap();
703     assert_eq!(5, all_languages.len());
704
705     // change user lang to finnish, should only show single finnish comment
706     let finnish_id = Language::read_id_from_code(conn, "fi").unwrap();
707     LocalUserLanguage::update(conn, vec![finnish_id], data.inserted_local_user.id).unwrap();
708     let finnish_comment = CommentQuery::builder()
709       .conn(conn)
710       .local_user(Some(&data.inserted_local_user))
711       .build()
712       .list()
713       .unwrap();
714     assert_eq!(1, finnish_comment.len());
715     assert_eq!(
716       data.inserted_comment_2.content,
717       finnish_comment[0].comment.content
718     );
719     assert_eq!(finnish_id, finnish_comment[0].comment.language_id);
720
721     // now show all comments with undetermined language (which is the default value)
722     let undetermined_id = Language::read_id_from_code(conn, "und").unwrap();
723     LocalUserLanguage::update(conn, vec![undetermined_id], data.inserted_local_user.id).unwrap();
724     let undetermined_comment = CommentQuery::builder()
725       .conn(conn)
726       .local_user(Some(&data.inserted_local_user))
727       .build()
728       .list()
729       .unwrap();
730     assert_eq!(3, undetermined_comment.len());
731
732     cleanup(data, conn);
733   }
734
735   fn cleanup(data: Data, conn: &mut PgConnection) {
736     CommentLike::remove(conn, data.inserted_person.id, data.inserted_comment_0.id).unwrap();
737     Comment::delete(conn, data.inserted_comment_0.id).unwrap();
738     Comment::delete(conn, data.inserted_comment_1.id).unwrap();
739     Post::delete(conn, data.inserted_post.id).unwrap();
740     Community::delete(conn, data.inserted_community.id).unwrap();
741     Person::delete(conn, data.inserted_person.id).unwrap();
742     Person::delete(conn, data.inserted_person_2.id).unwrap();
743     Instance::delete(conn, data.inserted_instance.id).unwrap();
744   }
745
746   fn expected_comment_view(data: &Data, conn: &mut PgConnection) -> CommentView {
747     let agg = CommentAggregates::read(conn, data.inserted_comment_0.id).unwrap();
748     CommentView {
749       creator_banned_from_community: false,
750       my_vote: None,
751       subscribed: SubscribedType::NotSubscribed,
752       saved: false,
753       creator_blocked: false,
754       comment: Comment {
755         id: data.inserted_comment_0.id,
756         content: "Comment 0".into(),
757         creator_id: data.inserted_person.id,
758         post_id: data.inserted_post.id,
759         removed: false,
760         deleted: false,
761         published: data.inserted_comment_0.published,
762         ap_id: data.inserted_comment_0.ap_id.clone(),
763         updated: None,
764         local: true,
765         distinguished: false,
766         path: data.inserted_comment_0.to_owned().path,
767         language_id: LanguageId(0),
768       },
769       creator: PersonSafe {
770         id: data.inserted_person.id,
771         name: "timmy".into(),
772         display_name: None,
773         published: data.inserted_person.published,
774         avatar: None,
775         actor_id: data.inserted_person.actor_id.to_owned(),
776         local: true,
777         banned: false,
778         deleted: false,
779         admin: false,
780         bot_account: false,
781         bio: None,
782         banner: None,
783         updated: None,
784         inbox_url: data.inserted_person.inbox_url.to_owned(),
785         shared_inbox_url: None,
786         matrix_user_id: None,
787         ban_expires: None,
788         instance_id: data.inserted_instance.id,
789       },
790       post: Post {
791         id: data.inserted_post.id,
792         name: data.inserted_post.name.to_owned(),
793         creator_id: data.inserted_person.id,
794         url: None,
795         body: None,
796         published: data.inserted_post.published,
797         updated: None,
798         community_id: data.inserted_community.id,
799         removed: false,
800         deleted: false,
801         locked: false,
802         stickied: false,
803         nsfw: false,
804         embed_title: None,
805         embed_description: None,
806         embed_video_url: None,
807         thumbnail_url: None,
808         ap_id: data.inserted_post.ap_id.to_owned(),
809         local: true,
810         language_id: Default::default(),
811       },
812       community: CommunitySafe {
813         id: data.inserted_community.id,
814         name: "test community 5".to_string(),
815         icon: None,
816         removed: false,
817         deleted: false,
818         nsfw: false,
819         actor_id: data.inserted_community.actor_id.to_owned(),
820         local: true,
821         title: "nada".to_owned(),
822         description: None,
823         updated: None,
824         banner: None,
825         hidden: false,
826         posting_restricted_to_mods: false,
827         published: data.inserted_community.published,
828         instance_id: data.inserted_instance.id,
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 }