@ -1,12 +1,12 @@
use crate ::structs ::CommentView ;
use diesel ::{ dsl ::* , result ::Error , * } ;
use diesel_ltree ::{ nlevel , subpath , Ltree , LtreeExtensions } ;
use lemmy_db_schema ::{
aggregates ::structs ::CommentAggregates ,
newtypes ::{ CommentId , CommunityId , DbUrl , PersonId , PostId } ,
schema ::{
comment ,
comment_aggregates ,
comment_alias_1 ,
comment_like ,
comment_saved ,
community ,
@ -14,28 +14,25 @@ use lemmy_db_schema::{
community_follower ,
community_person_ban ,
person ,
person_alias_1 ,
person_block ,
post ,
} ,
source ::{
comment ::{ Comment , Comment Alias1, Comment Saved} ,
comment ::{ Comment , Comment Saved} ,
community ::{ Community , CommunityFollower , CommunityPersonBan , CommunitySafe } ,
person ::{ Person , Person Alias1, Person Safe, PersonSafeAlias1 } ,
person ::{ Person , Person Safe} ,
person_block ::PersonBlock ,
post ::Post ,
} ,
traits ::{ MaybeOptional , ToSafe , ViewToVec } ,
utils ::{ functions ::hot_rank , fuzzy_search , limit_and_offset_unlimited } ,
CommentSortType ,
ListingType ,
SortType ,
} ;
type CommentViewTuple = (
Comment ,
PersonSafe ,
Option < CommentAlias1 > ,
Option < PersonSafeAlias1 > ,
Post ,
CommunitySafe ,
CommentAggregates ,
@ -58,8 +55,6 @@ impl CommentView {
let (
comment ,
creator ,
_parent_comment ,
recipient ,
post ,
community ,
counts ,
@ -71,9 +66,6 @@ impl CommentView {
) = comment ::table
. find ( comment_id )
. inner_join ( person ::table )
// recipient here
. left_join ( comment_alias_1 ::table . on ( comment_alias_1 ::id . nullable ( ) . eq ( comment ::parent_id ) ) )
. left_join ( person_alias_1 ::table . on ( person_alias_1 ::id . eq ( comment_alias_1 ::creator_id ) ) )
. inner_join ( post ::table )
. inner_join ( community ::table . on ( post ::community_id . eq ( community ::id ) ) )
. inner_join ( comment_aggregates ::table )
@ -120,8 +112,6 @@ impl CommentView {
. select ( (
comment ::all_columns ,
Person ::safe_columns_tuple ( ) ,
comment_alias_1 ::all_columns . nullable ( ) ,
PersonAlias1 ::safe_columns_tuple ( ) . nullable ( ) ,
post ::all_columns ,
Community ::safe_columns_tuple ( ) ,
comment_aggregates ::all_columns ,
@ -143,7 +133,6 @@ impl CommentView {
Ok ( CommentView {
comment ,
recipient ,
post ,
creator ,
community ,
@ -155,73 +144,24 @@ impl CommentView {
my_vote ,
} )
}
/// Gets the recipient person id.
/// If there is no parent comment, its the post creator
pub fn get_recipient_id ( & self ) -> PersonId {
match & self . recipient {
Some ( parent_commenter ) = > parent_commenter . id ,
None = > self . post . creator_id ,
}
}
/// Gets the number of unread replies
pub fn get_unread_replies ( conn : & PgConnection , my_person_id : PersonId ) -> Result < i64 , Error > {
use diesel ::dsl ::* ;
comment ::table
// recipient here
. left_join ( comment_alias_1 ::table . on ( comment_alias_1 ::id . nullable ( ) . eq ( comment ::parent_id ) ) )
. left_join ( person_alias_1 ::table . on ( person_alias_1 ::id . eq ( comment_alias_1 ::creator_id ) ) )
. inner_join ( post ::table )
. inner_join ( community ::table . on ( post ::community_id . eq ( community ::id ) ) )
. left_join (
person_block ::table . on (
comment ::creator_id
. eq ( person_block ::target_id )
. and ( person_block ::person_id . eq ( my_person_id ) ) ,
) ,
)
. left_join (
community_block ::table . on (
community ::id
. eq ( community_block ::community_id )
. and ( community_block ::person_id . eq ( my_person_id ) ) ,
) ,
)
. filter ( person_alias_1 ::id . eq ( my_person_id ) ) // Gets the comment replies
. or_filter (
comment ::parent_id
. is_null ( )
. and ( post ::creator_id . eq ( my_person_id ) ) ,
) // Gets the top level replies
. filter ( comment ::read . eq ( false ) )
. filter ( comment ::deleted . eq ( false ) )
. filter ( comment ::removed . eq ( false ) )
// Don't show blocked communities or persons
. filter ( community_block ::person_id . is_null ( ) )
. filter ( person_block ::person_id . is_null ( ) )
. select ( count ( comment ::id ) )
. first ::< i64 > ( conn )
}
}
pub struct CommentQueryBuilder < ' a > {
conn : & ' a PgConnection ,
listing_type : Option < ListingType > ,
sort : Option < SortType> ,
sort : Option < CommentSortType > ,
community_id : Option < CommunityId > ,
community_actor_id : Option < DbUrl > ,
post_id : Option < PostId > ,
parent_path : Option < Ltree > ,
creator_id : Option < PersonId > ,
recipient_id : Option < PersonId > ,
my_person_id : Option < PersonId > ,
search_term : Option < String > ,
saved_only : Option < bool > ,
unread_only : Option < bool > ,
show_bot_accounts : Option < bool > ,
page : Option < i64 > ,
limit : Option < i64 > ,
max_depth : Option < i32 > ,
}
impl < ' a > CommentQueryBuilder < ' a > {
@ -233,15 +173,15 @@ impl<'a> CommentQueryBuilder<'a> {
community_id : None ,
community_actor_id : None ,
post_id : None ,
parent_path : None ,
creator_id : None ,
recipient_id : None ,
my_person_id : None ,
search_term : None ,
saved_only : None ,
unread_only : None ,
show_bot_accounts : None ,
page : None ,
limit : None ,
max_depth : None ,
}
}
@ -250,7 +190,7 @@ impl<'a> CommentQueryBuilder<'a> {
self
}
pub fn sort < T : MaybeOptional < SortType> > ( mut self , sort : T ) -> Self {
pub fn sort < T : MaybeOptional < Comment SortType> > ( mut self , sort : T ) -> Self {
self . sort = sort . get_optional ( ) ;
self
}
@ -265,11 +205,6 @@ impl<'a> CommentQueryBuilder<'a> {
self
}
pub fn recipient_id < T : MaybeOptional < PersonId > > ( mut self , recipient_id : T ) -> Self {
self . recipient_id = recipient_id . get_optional ( ) ;
self
}
pub fn community_id < T : MaybeOptional < CommunityId > > ( mut self , community_id : T ) -> Self {
self . community_id = community_id . get_optional ( ) ;
self
@ -295,13 +230,13 @@ impl<'a> CommentQueryBuilder<'a> {
self
}
pub fn unread_only < T : MaybeOptional < bool > > ( mut self , unread_only : T ) -> Self {
self . unread_only = unread_only . get_optional ( ) ;
pub fn show_bot_accounts < T : MaybeOptional < bool > > ( mut self , show_bot_accounts : T ) -> Self {
self . show_bot_accounts = show_bot_accounts . get_optional ( ) ;
self
}
pub fn show_bot_accounts< T : MaybeOptional < bool > > ( mut self , show_bot_accounts : T ) -> Self {
self . show_bot_accounts = show_bot_accounts . get_optional ( ) ;
pub fn parent_path< T : MaybeOptional < Ltree > > ( mut self , parent_path : T ) -> Self {
self . parent_path = parent_path . get_optional ( ) ;
self
}
@ -315,6 +250,11 @@ impl<'a> CommentQueryBuilder<'a> {
self
}
pub fn max_depth < T : MaybeOptional < i32 > > ( mut self , max_depth : T ) -> Self {
self . max_depth = max_depth . get_optional ( ) ;
self
}
pub fn list ( self ) -> Result < Vec < CommentView > , Error > {
use diesel ::dsl ::* ;
@ -323,9 +263,6 @@ impl<'a> CommentQueryBuilder<'a> {
let mut query = comment ::table
. inner_join ( person ::table )
// recipient here
. left_join ( comment_alias_1 ::table . on ( comment_alias_1 ::id . nullable ( ) . eq ( comment ::parent_id ) ) )
. left_join ( person_alias_1 ::table . on ( person_alias_1 ::id . eq ( comment_alias_1 ::creator_id ) ) )
. inner_join ( post ::table )
. inner_join ( community ::table . on ( post ::community_id . eq ( community ::id ) ) )
. inner_join ( comment_aggregates ::table )
@ -379,8 +316,6 @@ impl<'a> CommentQueryBuilder<'a> {
. select ( (
comment ::all_columns ,
Person ::safe_columns_tuple ( ) ,
comment_alias_1 ::all_columns . nullable ( ) ,
PersonAlias1 ::safe_columns_tuple ( ) . nullable ( ) ,
post ::all_columns ,
Community ::safe_columns_tuple ( ) ,
comment_aggregates ::all_columns ,
@ -392,24 +327,6 @@ impl<'a> CommentQueryBuilder<'a> {
) )
. into_boxed ( ) ;
// The replies
if let Some ( recipient_id ) = self . recipient_id {
query = query
// TODO needs lots of testing
. filter ( person_alias_1 ::id . eq ( recipient_id ) ) // Gets the comment replies
. or_filter (
comment ::parent_id
. is_null ( )
. and ( post ::creator_id . eq ( recipient_id ) ) ,
) // Gets the top level replies
. filter ( comment ::deleted . eq ( false ) )
. filter ( comment ::removed . eq ( false ) ) ;
}
if self . unread_only . unwrap_or ( false ) {
query = query . filter ( comment ::read . eq ( false ) ) ;
}
if let Some ( creator_id ) = self . creator_id {
query = query . filter ( comment ::creator_id . eq ( creator_id ) ) ;
} ;
@ -418,6 +335,10 @@ impl<'a> CommentQueryBuilder<'a> {
query = query . filter ( comment ::post_id . eq ( post_id ) ) ;
} ;
if let Some ( parent_path ) = self . parent_path . as_ref ( ) {
query = query . filter ( comment ::path . contained_by ( parent_path ) ) ;
} ;
if let Some ( search_term ) = self . search_term {
query = query . filter ( comment ::content . ilike ( fuzzy_search ( & search_term ) ) ) ;
} ;
@ -460,36 +381,47 @@ impl<'a> CommentQueryBuilder<'a> {
query = query . filter ( person ::bot_account . eq ( false ) ) ;
} ;
query = match self . sort . unwrap_or ( SortType ::New ) {
SortType ::Hot | SortType ::Active = > query
. order_by ( hot_rank ( comment_aggregates ::score , comment_aggregates ::published ) . desc ( ) )
. then_order_by ( comment_aggregates ::published . desc ( ) ) ,
SortType ::New | SortType ::MostComments | SortType ::NewComments = > {
query . order_by ( comment ::published . desc ( ) )
}
SortType ::TopAll = > query . order_by ( comment_aggregates ::score . desc ( ) ) ,
SortType ::TopYear = > query
. filter ( comment ::published . gt ( now - 1. years ( ) ) )
. order_by ( comment_aggregates ::score . desc ( ) ) ,
SortType ::TopMonth = > query
. filter ( comment ::published . gt ( now - 1. months ( ) ) )
. order_by ( comment_aggregates ::score . desc ( ) ) ,
SortType ::TopWeek = > query
. filter ( comment ::published . gt ( now - 1. weeks ( ) ) )
. order_by ( comment_aggregates ::score . desc ( ) ) ,
SortType ::TopDay = > query
. filter ( comment ::published . gt ( now - 1. days ( ) ) )
. order_by ( comment_aggregates ::score . desc ( ) ) ,
} ;
// Don't show blocked communities or persons
if self . my_person_id . is_some ( ) {
query = query . filter ( community_block ::person_id . is_null ( ) ) ;
query = query . filter ( person_block ::person_id . is_null ( ) ) ;
}
// Don't use the regular error-checking one, many more comments must ofter be fetched.
let ( limit , offset ) = limit_and_offset_unlimited ( self . page , self . limit ) ;
// A Max depth given means its a tree fetch
let ( limit , offset ) = if let Some ( max_depth ) = self . max_depth {
let depth_limit = if let Some ( parent_path ) = self . parent_path . as_ref ( ) {
parent_path . 0. split ( '.' ) . count ( ) as i32 + max_depth
// Add one because of root "0"
} else {
max_depth + 1
} ;
query = query . filter ( nlevel ( comment ::path ) . le ( depth_limit ) ) ;
// Always order by the parent path first
query = query . order_by ( subpath ( comment ::path , 0 , - 1 ) ) ;
// TODO limit question. Limiting does not work for comment threads ATM, only max_depth
// For now, don't do any limiting for tree fetches
// https://stackoverflow.com/questions/72983614/postgres-ltree-how-to-limit-the-max-number-of-children-at-any-given-level
// Don't use the regular error-checking one, many more comments must ofter be fetched.
// This does not work for comment trees, and the limit should be manually set to a high number
//
// If a max depth is given, then you know its a tree fetch, and limits should be ignored
( i64 ::MAX , 0 )
} else {
limit_and_offset_unlimited ( self . page , self . limit )
} ;
query = match self . sort . unwrap_or ( CommentSortType ::Hot ) {
CommentSortType ::Hot = > query
. then_order_by ( hot_rank ( comment_aggregates ::score , comment_aggregates ::published ) . desc ( ) )
. then_order_by ( comment_aggregates ::published . desc ( ) ) ,
CommentSortType ::New = > query . then_order_by ( comment ::published . desc ( ) ) ,
CommentSortType ::Old = > query . then_order_by ( comment ::published . asc ( ) ) ,
CommentSortType ::Top = > query . order_by ( comment_aggregates ::score . desc ( ) ) ,
} ;
// Note: deleted and removed comments are done on the front side
let res = query
@ -509,15 +441,14 @@ impl ViewToVec for CommentView {
. map ( | a | Self {
comment : a . 0. to_owned ( ) ,
creator : a . 1. to_owned ( ) ,
recipient : a . 3. to_owned ( ) ,
post : a . 4. to_owned ( ) ,
community : a . 5. to_owned ( ) ,
counts : a . 6. to_owned ( ) ,
creator_banned_from_community : a . 7. is_some ( ) ,
subscribed : CommunityFollower ::to_subscribed_type ( & a . 8 ) ,
saved : a . 9. is_some ( ) ,
creator_blocked : a . 10. is_some ( ) ,
my_vote : a . 11 ,
post : a . 2. to_owned ( ) ,
community : a . 3. to_owned ( ) ,
counts : a . 4. to_owned ( ) ,
creator_banned_from_community : a . 5. is_some ( ) ,
subscribed : CommunityFollower ::to_subscribed_type ( & a . 6 ) ,
saved : a . 7. is_some ( ) ,
creator_blocked : a . 8. is_some ( ) ,
my_vote : a . 9 ,
} )
. collect ::< Vec < Self > > ( )
}
@ -574,24 +505,72 @@ mod tests {
let inserted_post = Post ::create ( & conn , & new_post ) . unwrap ( ) ;
let comment_form = CommentForm {
content : "A test comment 32" . into ( ) ,
// Create a comment tree with this hierarchy
// 0
// \ \
// 1 2
// \
// 3 4
// \
// 5
let comment_form_0 = CommentForm {
content : "Comment 0" . into ( ) ,
creator_id : inserted_person . id ,
post_id : inserted_post . id ,
.. CommentForm ::default ( )
} ;
let inserted_comment = Comment ::create ( & conn , & comment_form ) . unwrap ( ) ;
let inserted_comment _0 = Comment ::create ( & conn , & comment_form _0, None ) . unwrap ( ) ;
let comment_form_2 = CommentForm {
content : " A test blocked comment". into ( ) ,
let comment_form_ 1 = CommentForm {
content : " Comment 1, A test blocked comment". into ( ) ,
creator_id : inserted_person_2 . id ,
post_id : inserted_post . id ,
parent_id : Some ( inserted_comment . id ) ,
.. CommentForm ::default ( )
} ;
let inserted_comment_2 = Comment ::create ( & conn , & comment_form_2 ) . unwrap ( ) ;
let inserted_comment_1 =
Comment ::create ( & conn , & comment_form_1 , Some ( & inserted_comment_0 . path ) ) . unwrap ( ) ;
let comment_form_2 = CommentForm {
content : "Comment 2" . into ( ) ,
creator_id : inserted_person . id ,
post_id : inserted_post . id ,
.. CommentForm ::default ( )
} ;
let inserted_comment_2 =
Comment ::create ( & conn , & comment_form_2 , Some ( & inserted_comment_0 . path ) ) . unwrap ( ) ;
let comment_form_3 = CommentForm {
content : "Comment 3" . into ( ) ,
creator_id : inserted_person . id ,
post_id : inserted_post . id ,
.. CommentForm ::default ( )
} ;
let _inserted_comment_3 =
Comment ::create ( & conn , & comment_form_3 , Some ( & inserted_comment_1 . path ) ) . unwrap ( ) ;
let comment_form_4 = CommentForm {
content : "Comment 4" . into ( ) ,
creator_id : inserted_person . id ,
post_id : inserted_post . id ,
.. CommentForm ::default ( )
} ;
let inserted_comment_4 =
Comment ::create ( & conn , & comment_form_4 , Some ( & inserted_comment_1 . path ) ) . unwrap ( ) ;
let comment_form_5 = CommentForm {
content : "Comment 5" . into ( ) ,
creator_id : inserted_person . id ,
post_id : inserted_post . id ,
.. CommentForm ::default ( )
} ;
let _inserted_comment_5 =
Comment ::create ( & conn , & comment_form_5 , Some ( & inserted_comment_4 . path ) ) . unwrap ( ) ;
let timmy_blocks_sara_form = PersonBlockForm {
person_id : inserted_person . id ,
@ -610,7 +589,7 @@ mod tests {
assert_eq! ( expected_block , inserted_block ) ;
let comment_like_form = CommentLikeForm {
comment_id : inserted_comment . id ,
comment_id : inserted_comment _0 . id ,
post_id : inserted_post . id ,
person_id : inserted_person . id ,
score : 1 ,
@ -618,8 +597,9 @@ mod tests {
let _inserted_comment_like = CommentLike ::like ( & conn , & comment_like_form ) . unwrap ( ) ;
let agg = CommentAggregates ::read ( & conn , inserted_comment . id ) . unwrap ( ) ;
let agg = CommentAggregates ::read ( & conn , inserted_comment _0 . id ) . unwrap ( ) ;
let top_path = inserted_comment_0 . to_owned ( ) . path ;
let expected_comment_view_no_person = CommentView {
creator_banned_from_community : false ,
my_vote : None ,
@ -627,18 +607,17 @@ mod tests {
saved : false ,
creator_blocked : false ,
comment : Comment {
id : inserted_comment . id ,
content : " A test comment 32 ". into ( ) ,
id : inserted_comment _0 . id ,
content : " Comment 0 ". into ( ) ,
creator_id : inserted_person . id ,
post_id : inserted_post . id ,
parent_id : None ,
removed : false ,
deleted : false ,
read : false ,
published : inserted_comment . published ,
ap_id : inserted_comment . ap_id ,
published : inserted_comment_0 . published ,
ap_id : inserted_comment_0 . ap_id ,
updated : None ,
local : true ,
path : top_path ,
} ,
creator : PersonSafe {
id : inserted_person . id ,
@ -660,7 +639,6 @@ mod tests {
matrix_user_id : None ,
ban_expires : None ,
} ,
recipient : None ,
post : Post {
id : inserted_post . id ,
name : inserted_post . name . to_owned ( ) ,
@ -701,11 +679,12 @@ mod tests {
} ,
counts : CommentAggregates {
id : agg . id ,
comment_id : inserted_comment . id ,
comment_id : inserted_comment _0 . id ,
score : 1 ,
upvotes : 1 ,
downvotes : 0 ,
published : agg . published ,
child_count : 5 ,
} ,
} ;
@ -717,37 +696,95 @@ mod tests {
. list ( )
. unwrap ( ) ;
assert_eq! (
expected_comment_view_no_person ,
read_comment_views_no_person [ 0 ]
) ;
let read_comment_views_with_person = CommentQueryBuilder ::create ( & conn )
. post_id ( inserted_post . id )
. my_person_id ( inserted_person . id )
. list ( )
. unwrap ( ) ;
assert_eq! (
expected_comment_view_with_person ,
read_comment_views_with_person [ 0 ]
) ;
// Make sure its 1, not showing the blocked comment
assert_eq! ( 5 , read_comment_views_with_person . len ( ) ) ;
let read_comment_from_blocked_person =
CommentView ::read ( & conn , inserted_comment_2 . id , Some ( inserted_person . id ) ) . unwrap ( ) ;
CommentView ::read ( & conn , inserted_comment_ 1 . id , Some ( inserted_person . id ) ) . unwrap ( ) ;
let like_removed = CommentLike ::remove ( & conn , inserted_person . id , inserted_comment . id ) . unwrap ( ) ;
let num_deleted = Comment ::delete ( & conn , inserted_comment . id ) . unwrap ( ) ;
Comment ::delete ( & conn , inserted_comment_2 . id ) . unwrap ( ) ;
Post ::delete ( & conn , inserted_post . id ) . unwrap ( ) ;
Community ::delete ( & conn , inserted_community . id ) . unwrap ( ) ;
Person ::delete ( & conn , inserted_person . id ) . unwrap ( ) ;
Person ::delete ( & conn , inserted_person_2 . id ) . unwrap ( ) ;
// Make sure block set the creator blocked
assert! ( read_comment_from_blocked_person . creator_blocked ) ;
// Make sure its 1, not showing the blocked comment
assert_eq! ( 1 , read_comment_views_with_person . len ( ) ) ;
let top_path = inserted_comment_0 . path ;
let read_comment_views_top_path = CommentQueryBuilder ::create ( & conn )
. post_id ( inserted_post . id )
. parent_path ( top_path )
. list ( )
. unwrap ( ) ;
let child_path = inserted_comment_1 . to_owned ( ) . path ;
let read_comment_views_child_path = CommentQueryBuilder ::create ( & conn )
. post_id ( inserted_post . id )
. parent_path ( child_path )
. list ( )
. unwrap ( ) ;
// Make sure the comment parent-limited fetch is correct
assert_eq! ( 6 , read_comment_views_top_path . len ( ) ) ;
assert_eq! ( 4 , read_comment_views_child_path . len ( ) ) ;
// Make sure it contains the parent, but not the comment from the other tree
let child_comments = read_comment_views_child_path
. into_iter ( )
. map ( | c | c . comment )
. collect ::< Vec < Comment > > ( ) ;
assert! ( child_comments . contains ( & inserted_comment_1 ) ) ;
assert! ( ! child_comments . contains ( & inserted_comment_2 ) ) ;
let read_comment_views_top_max_depth = CommentQueryBuilder ::create ( & conn )
. post_id ( inserted_post . id )
. max_depth ( 1 )
. list ( )
. unwrap ( ) ;
// Make sure a depth limited one only has the top comment
assert_eq! (
expected_comment_view_no_person ,
read_comment_views_no_person [ 1 ]
) ;
assert_eq! (
expected_comment_view_with_person ,
read_comment_views_with_person [ 0 ]
read_comment_views_top_max_depth [ 0 ]
) ;
assert_eq! ( 1 , read_comment_views_top_max_depth . len ( ) ) ;
// Make sure block set the creator blocked
assert! ( read_comment_from_blocked_person . creator_blocked ) ;
let child_path = inserted_comment_1 . path ;
let read_comment_views_parent_max_depth = CommentQueryBuilder ::create ( & conn )
. post_id ( inserted_post . id )
. parent_path ( child_path )
. max_depth ( 1 )
. sort ( CommentSortType ::New )
. list ( )
. unwrap ( ) ;
// Make sure a depth limited one, and given child comment 1, has 3
assert! ( read_comment_views_parent_max_depth [ 2 ]
. comment
. content
. eq ( "Comment 3" ) ) ;
assert_eq! ( 3 , read_comment_views_parent_max_depth . len ( ) ) ;
// Delete everything
let like_removed =
CommentLike ::remove ( & conn , inserted_person . id , inserted_comment_0 . id ) . unwrap ( ) ;
let num_deleted = Comment ::delete ( & conn , inserted_comment_0 . id ) . unwrap ( ) ;
Comment ::delete ( & conn , inserted_comment_1 . id ) . unwrap ( ) ;
Post ::delete ( & conn , inserted_post . id ) . unwrap ( ) ;
Community ::delete ( & conn , inserted_community . id ) . unwrap ( ) ;
Person ::delete ( & conn , inserted_person . id ) . unwrap ( ) ;
Person ::delete ( & conn , inserted_person_2 . id ) . unwrap ( ) ;
assert_eq! ( 1 , num_deleted ) ;
assert_eq! ( 1 , like_removed ) ;