Add federated comment and post undo like.

This commit is contained in:
Dessalines 2020-05-03 20:34:04 -04:00
parent 6b55f22797
commit cf427543e5
7 changed files with 279 additions and 5 deletions

View file

@ -561,7 +561,7 @@ impl Perform for Oper<CreateCommentLike> {
comment.send_dislike(&user, &conn)?; comment.send_dislike(&user, &conn)?;
} }
} else { } else {
// TODO tombstone the like comment.send_undo_like(&user, &conn)?;
} }
// Have to refetch the comment to get the current state // Have to refetch the comment to get the current state

View file

@ -397,7 +397,7 @@ impl Perform for Oper<CreatePostLike> {
post.send_dislike(&user, &conn)?; post.send_dislike(&user, &conn)?;
} }
} else { } else {
// TODO tombstone the post like post.send_undo_like(&user, &conn)?;
} }
let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) { let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) {

View file

@ -413,4 +413,51 @@ impl ApubLikeableType for Comment {
)?; )?;
Ok(()) Ok(())
} }
fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let note = self.to_apub(&conn)?;
let post = Post::read(&conn, self.post_id)?;
let community = Community::read(&conn, post.community_id)?;
let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
let mut like = Like::new();
populate_object_props(&mut like.object_props, &community.get_followers_url(), &id)?;
like
.like_props
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(note)?;
// TODO
// Undo that fake activity
let undo_id = format!("{}/undo/like/{}", self.ap_id, uuid::Uuid::new_v4());
let mut undo = Undo::default();
populate_object_props(
&mut undo.object_props,
&community.get_followers_url(),
&undo_id,
)?;
undo
.undo_props
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(like)?;
// Insert the sent activity into the activity table
let activity_form = activity::ActivityForm {
user_id: creator.id,
data: serde_json::to_value(&undo)?,
local: true,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
send_activity(
&undo,
&creator.private_key.as_ref().unwrap(),
&creator.actor_id,
community.get_follower_inboxes(&conn)?,
)?;
Ok(())
}
} }

View file

@ -208,7 +208,7 @@ pub trait ApubObjectType {
pub trait ApubLikeableType { pub trait ApubLikeableType {
fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
// TODO add send_undo_like / undo_dislike fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
} }
pub fn get_shared_inbox(actor_id: &str) -> String { pub fn get_shared_inbox(actor_id: &str) -> String {

View file

@ -416,4 +416,50 @@ impl ApubLikeableType for Post {
)?; )?;
Ok(()) Ok(())
} }
fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
let page = self.to_apub(conn)?;
let community = Community::read(conn, self.community_id)?;
let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
let mut like = Like::new();
populate_object_props(&mut like.object_props, &community.get_followers_url(), &id)?;
like
.like_props
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(page)?;
// TODO
// Undo that fake activity
let undo_id = format!("{}/undo/like/{}", self.ap_id, uuid::Uuid::new_v4());
let mut undo = Undo::default();
populate_object_props(
&mut undo.object_props,
&community.get_followers_url(),
&undo_id,
)?;
undo
.undo_props
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
.set_object_base_box(like)?;
// Insert the sent activity into the activity table
let activity_form = activity::ActivityForm {
user_id: creator.id,
data: serde_json::to_value(&undo)?,
local: true,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
send_activity(
&undo,
&creator.private_key.as_ref().unwrap(),
&creator.actor_id,
community.get_follower_inboxes(&conn)?,
)?;
Ok(())
}
} }

View file

@ -91,6 +91,9 @@ pub async fn shared_inbox(
(SharedAcceptedObjects::Undo(u), Some("Remove")) => { (SharedAcceptedObjects::Undo(u), Some("Remove")) => {
receive_undo_remove(&u, &request, &conn, chat_server) receive_undo_remove(&u, &request, &conn, chat_server)
} }
(SharedAcceptedObjects::Undo(u), Some("Like")) => {
receive_undo_like(&u, &request, &conn, chat_server)
}
_ => Err(format_err!("Unknown incoming activity type.")), _ => Err(format_err!("Unknown incoming activity type.")),
} }
} }
@ -1424,3 +1427,141 @@ fn receive_undo_remove_community(
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())
} }
fn receive_undo_like(
undo: &Undo,
request: &HttpRequest,
conn: &PgConnection,
chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> {
let like = undo
.undo_props
.get_object_base_box()
.to_owned()
.unwrap()
.to_owned()
.into_concrete::<Like>()?;
let type_ = like
.like_props
.get_object_base_box()
.to_owned()
.unwrap()
.kind()
.unwrap();
match type_ {
"Note" => receive_undo_like_comment(&like, &request, &conn, chat_server),
"Page" => receive_undo_like_post(&like, &request, &conn, chat_server),
d => Err(format_err!("Undo Delete type {} not supported", d)),
}
}
fn receive_undo_like_comment(
like: &Like,
request: &HttpRequest,
conn: &PgConnection,
chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> {
let note = like
.like_props
.get_object_base_box()
.to_owned()
.unwrap()
.to_owned()
.into_concrete::<Note>()?;
let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
verify(request, &user.public_key.unwrap())?;
// Insert the received activity into the activity table
let activity_form = activity::ActivityForm {
user_id: user.id,
data: serde_json::to_value(&like)?,
local: false,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
let comment = CommentForm::from_apub(&note, &conn)?;
let comment_id = Comment::read_from_apub_id(conn, &comment.ap_id)?.id;
let like_form = CommentLikeForm {
comment_id,
post_id: comment.post_id,
user_id: user.id,
score: 0,
};
CommentLike::remove(&conn, &like_form)?;
// Refetch the view
let comment_view = CommentView::read(&conn, comment_id, None)?;
// TODO get those recipient actor ids from somewhere
let recipient_ids = vec![];
let res = CommentResponse {
comment: comment_view,
recipient_ids,
};
chat_server.do_send(SendComment {
op: UserOperation::CreateCommentLike,
comment: res,
my_id: None,
});
Ok(HttpResponse::Ok().finish())
}
fn receive_undo_like_post(
like: &Like,
request: &HttpRequest,
conn: &PgConnection,
chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> {
let page = like
.like_props
.get_object_base_box()
.to_owned()
.unwrap()
.to_owned()
.into_concrete::<Page>()?;
let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
verify(request, &user.public_key.unwrap())?;
// Insert the received activity into the activity table
let activity_form = activity::ActivityForm {
user_id: user.id,
data: serde_json::to_value(&like)?,
local: false,
updated: None,
};
activity::Activity::create(&conn, &activity_form)?;
let post = PostForm::from_apub(&page, conn)?;
let post_id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
let like_form = PostLikeForm {
post_id,
user_id: user.id,
score: 1,
};
PostLike::remove(&conn, &like_form)?;
// Refetch the view
let post_view = PostView::read(&conn, post_id, None)?;
let res = PostResponse { post: post_view };
chat_server.do_send(SendPost {
op: UserOperation::CreatePostLike,
post: res,
my_id: None,
});
Ok(HttpResponse::Ok().finish())
}

View file

@ -16,6 +16,8 @@ import {
CommunityForm, CommunityForm,
GetCommunityForm, GetCommunityForm,
GetCommunityResponse, GetCommunityResponse,
CommentLikeForm,
CreatePostLikeForm,
} from '../interfaces'; } from '../interfaces';
let lemmyAlphaUrl = 'http://localhost:8540'; let lemmyAlphaUrl = 'http://localhost:8540';
@ -163,11 +165,28 @@ describe('main', () => {
} }
).then(d => d.json()); ).then(d => d.json());
let unlikePostForm: CreatePostLikeForm = {
post_id: createResponse.post.id,
score: 0,
auth: lemmyAlphaAuth,
};
expect(createResponse.post.name).toBe(name); expect(createResponse.post.name).toBe(name);
expect(createResponse.post.community_local).toBe(false); expect(createResponse.post.community_local).toBe(false);
expect(createResponse.post.creator_local).toBe(true); expect(createResponse.post.creator_local).toBe(true);
expect(createResponse.post.score).toBe(1); expect(createResponse.post.score).toBe(1);
let unlikePostRes: PostResponse = await fetch(
`${lemmyAlphaApiUrl}/post/like`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: wrapper(unlikePostForm),
}
).then(d => d.json());
expect(unlikePostRes.post.score).toBe(0);
let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`; let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
let getPostRes: GetPostResponse = await fetch(getPostUrl, { let getPostRes: GetPostResponse = await fetch(getPostUrl, {
method: 'GET', method: 'GET',
@ -176,7 +195,7 @@ describe('main', () => {
expect(getPostRes.post.name).toBe(name); expect(getPostRes.post.name).toBe(name);
expect(getPostRes.post.community_local).toBe(true); expect(getPostRes.post.community_local).toBe(true);
expect(getPostRes.post.creator_local).toBe(false); expect(getPostRes.post.creator_local).toBe(false);
expect(getPostRes.post.score).toBe(1); expect(getPostRes.post.score).toBe(0);
}); });
}); });
@ -243,6 +262,27 @@ describe('main', () => {
expect(createResponse.comment.creator_local).toBe(true); expect(createResponse.comment.creator_local).toBe(true);
expect(createResponse.comment.score).toBe(1); expect(createResponse.comment.score).toBe(1);
// Do an unlike, to test it
let unlikeCommentForm: CommentLikeForm = {
comment_id: createResponse.comment.id,
score: 0,
post_id: 2,
auth: lemmyAlphaAuth,
};
let unlikeCommentRes: CommentResponse = await fetch(
`${lemmyAlphaApiUrl}/comment/like`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: wrapper(unlikeCommentForm),
}
).then(d => d.json());
expect(unlikeCommentRes.comment.score).toBe(0);
let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`; let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
let getPostRes: GetPostResponse = await fetch(getPostUrl, { let getPostRes: GetPostResponse = await fetch(getPostUrl, {
method: 'GET', method: 'GET',
@ -251,7 +291,7 @@ describe('main', () => {
expect(getPostRes.comments[0].content).toBe(content); expect(getPostRes.comments[0].content).toBe(content);
expect(getPostRes.comments[0].community_local).toBe(true); expect(getPostRes.comments[0].community_local).toBe(true);
expect(getPostRes.comments[0].creator_local).toBe(false); expect(getPostRes.comments[0].creator_local).toBe(false);
expect(getPostRes.comments[0].score).toBe(1); expect(getPostRes.comments[0].score).toBe(0);
// Now do beta replying to that comment, as a child comment // Now do beta replying to that comment, as a child comment
let contentBeta = 'A child federated comment from beta'; let contentBeta = 'A child federated comment from beta';