Send Undo(Like) activity when post is unfavourited

This commit is contained in:
silverpill 2021-12-15 01:00:42 +00:00
parent 99f32e8202
commit cf69ac6eb2
5 changed files with 96 additions and 22 deletions

View file

@ -92,6 +92,7 @@ The following activities are supported:
- Create(Note) - Create(Note)
- Delete(Note) - Delete(Note)
- Like(Note) - Like(Note)
- Undo(Like)
- Announce(Note) - Announce(Note)
- Follow(Person) - Follow(Person)
- Update(Person) - Update(Person)

View file

@ -125,7 +125,7 @@ fn create_activity(
instance_url: &str, instance_url: &str,
actor_name: &str, actor_name: &str,
activity_type: &str, activity_type: &str,
activity_uuid: Option<Uuid>, internal_activity_id: Option<&Uuid>,
object: impl Serialize, object: impl Serialize,
recipients: Vec<String>, recipients: Vec<String>,
) -> Activity { ) -> Activity {
@ -135,7 +135,7 @@ fn create_activity(
); );
let activity_id = get_object_url( let activity_id = get_object_url(
instance_url, instance_url,
&activity_uuid.unwrap_or(new_uuid()), internal_activity_id.unwrap_or(&new_uuid()),
); );
Activity { Activity {
context: json!(AP_CONTEXT), context: json!(AP_CONTEXT),
@ -248,19 +248,39 @@ pub fn create_activity_note(
pub fn create_activity_like( pub fn create_activity_like(
instance_url: &str, instance_url: &str,
actor_profile: &DbActorProfile, actor_profile: &DbActorProfile,
object_id: &str, note_id: &str,
reaction_id: &Uuid,
) -> Activity { ) -> Activity {
let activity = create_activity( let activity = create_activity(
instance_url, instance_url,
&actor_profile.username, &actor_profile.username,
LIKE, LIKE,
None, Some(reaction_id),
object_id, note_id,
vec![AP_PUBLIC.to_string()], vec![AP_PUBLIC.to_string()],
); );
activity activity
} }
pub fn create_activity_undo_like(
instance_url: &str,
actor_profile: &DbActorProfile,
reaction_id: &Uuid,
) -> Activity {
let object_id = get_object_url(
instance_url,
reaction_id,
);
create_activity(
instance_url,
&actor_profile.username,
UNDO,
None,
object_id,
vec![AP_PUBLIC.to_string()],
)
}
pub fn create_activity_announce( pub fn create_activity_announce(
instance_url: &str, instance_url: &str,
actor_profile: &DbActorProfile, actor_profile: &DbActorProfile,
@ -317,7 +337,7 @@ pub fn create_activity_follow(
instance_url, instance_url,
&actor_profile.username, &actor_profile.username,
FOLLOW, FOLLOW,
Some(*follow_request_id), Some(follow_request_id),
object, object,
vec![target_actor_id.to_string()], vec![target_actor_id.to_string()],
); );

View file

@ -24,7 +24,11 @@ use crate::models::profiles::queries::{
update_profile, update_profile,
}; };
use crate::models::profiles::types::{DbActorProfile, ProfileUpdateData}; use crate::models::profiles::types::{DbActorProfile, ProfileUpdateData};
use crate::models::reactions::queries::create_reaction; use crate::models::reactions::queries::{
create_reaction,
get_reaction_by_activity_id,
delete_reaction,
};
use crate::models::relationships::queries::{ use crate::models::relationships::queries::{
follow_request_accepted, follow_request_accepted,
follow_request_rejected, follow_request_rejected,
@ -433,6 +437,16 @@ pub async fn receive_activity(
let target_profile = get_profile_by_acct(db_client, &target_username).await?; let target_profile = get_profile_by_acct(db_client, &target_username).await?;
unfollow(db_client, &source_profile.id, &target_profile.id).await?; unfollow(db_client, &source_profile.id, &target_profile.id).await?;
}, },
(UNDO, _) => {
// Undo(Like)
let object_id = get_object_id(activity.object)?;
let reaction = get_reaction_by_activity_id(db_client, &object_id).await?;
delete_reaction(
db_client,
&reaction.author_id,
&reaction.post_id,
).await?;
},
(UPDATE, PERSON) => { (UPDATE, PERSON) => {
let actor: Actor = serde_json::from_value(activity.object) let actor: Actor = serde_json::from_value(activity.object)
.map_err(|_| ValidationError("invalid actor data"))?; .map_err(|_| ValidationError("invalid actor data"))?;

View file

@ -7,6 +7,7 @@ use uuid::Uuid;
use crate::activitypub::activity::{ use crate::activitypub::activity::{
create_activity_note, create_activity_note,
create_activity_like, create_activity_like,
create_activity_undo_like,
create_activity_announce, create_activity_announce,
create_activity_delete_note, create_activity_delete_note,
}; };
@ -232,20 +233,20 @@ async fn favourite(
if !can_view_post(Some(&current_user), &post) { if !can_view_post(Some(&current_user), &post) {
return Err(HttpError::NotFoundError("post")); return Err(HttpError::NotFoundError("post"));
}; };
let reaction_created = match create_reaction( let maybe_reaction_created = match create_reaction(
db_client, &current_user.id, &status_id, None, db_client, &current_user.id, &status_id, None,
).await { ).await {
Ok(_) => { Ok(reaction) => {
post.reaction_count += 1; post.reaction_count += 1;
true Some(reaction)
}, },
Err(DatabaseError::AlreadyExists(_)) => false, // post already favourited Err(DatabaseError::AlreadyExists(_)) => None, // post already favourited
Err(other_error) => return Err(other_error.into()), Err(other_error) => return Err(other_error.into()),
}; };
get_reposted_posts(db_client, vec![&mut post]).await?; get_reposted_posts(db_client, vec![&mut post]).await?;
get_actions_for_posts(db_client, &current_user.id, vec![&mut post]).await?; get_actions_for_posts(db_client, &current_user.id, vec![&mut post]).await?;
if reaction_created { if let Some(reaction) = maybe_reaction_created {
let maybe_remote_actor = post.author.remote_actor() let maybe_remote_actor = post.author.remote_actor()
.map_err(|_| HttpError::InternalError)?; .map_err(|_| HttpError::InternalError)?;
if let Some(remote_actor) = maybe_remote_actor { if let Some(remote_actor) = maybe_remote_actor {
@ -255,6 +256,7 @@ async fn favourite(
&config.instance_url(), &config.instance_url(),
&current_user.profile, &current_user.profile,
object_id, object_id,
&reaction.id,
); );
deliver_activity(&config, &current_user, activity, vec![remote_actor]); deliver_activity(&config, &current_user, activity, vec![remote_actor]);
} }
@ -277,13 +279,33 @@ async fn unfavourite(
if !can_view_post(Some(&current_user), &post) { if !can_view_post(Some(&current_user), &post) {
return Err(HttpError::NotFoundError("post")); return Err(HttpError::NotFoundError("post"));
}; };
match delete_reaction(db_client, &current_user.id, &status_id).await { let maybe_reaction_deleted = match delete_reaction(
Ok(_) => post.reaction_count -= 1, db_client, &current_user.id, &status_id,
Err(DatabaseError::NotFound(_)) => (), // post not favourited ).await {
Ok(reaction_id) => {
post.reaction_count -= 1;
Some(reaction_id)
},
Err(DatabaseError::NotFound(_)) => None, // post not favourited
Err(other_error) => return Err(other_error.into()), Err(other_error) => return Err(other_error.into()),
} };
get_reposted_posts(db_client, vec![&mut post]).await?; get_reposted_posts(db_client, vec![&mut post]).await?;
get_actions_for_posts(db_client, &current_user.id, vec![&mut post]).await?; get_actions_for_posts(db_client, &current_user.id, vec![&mut post]).await?;
if let Some(reaction_id) = maybe_reaction_deleted {
let maybe_remote_actor = post.author.remote_actor()
.map_err(|_| HttpError::InternalError)?;
if let Some(remote_actor) = maybe_remote_actor {
// Federate
let activity = create_activity_undo_like(
&config.instance_url(),
&current_user.profile,
&reaction_id,
);
deliver_activity(&config, &current_user, activity, vec![remote_actor]);
};
};
let status = Status::from_post(post, &config.instance_url()); let status = Status::from_post(post, &config.instance_url());
Ok(HttpResponse::Ok().json(status)) Ok(HttpResponse::Ok().json(status))
} }

View file

@ -42,25 +42,42 @@ pub async fn create_reaction(
Ok(reaction) Ok(reaction)
} }
pub async fn get_reaction_by_activity_id(
db_client: &impl GenericClient,
activity_id: &str,
) -> Result<DbReaction, DatabaseError> {
let maybe_row = db_client.query_opt(
"
SELECT post_reaction
FROM post_reaction
WHERE activity_id = $1
",
&[&activity_id],
).await?;
let row = maybe_row.ok_or(DatabaseError::NotFound("reaction"))?;
let reaction = row.try_get("post_reaction")?;
Ok(reaction)
}
pub async fn delete_reaction( pub async fn delete_reaction(
db_client: &mut impl GenericClient, db_client: &mut impl GenericClient,
author_id: &Uuid, author_id: &Uuid,
post_id: &Uuid, post_id: &Uuid,
) -> Result<(), DatabaseError> { ) -> Result<Uuid, DatabaseError> {
let transaction = db_client.transaction().await?; let transaction = db_client.transaction().await?;
let deleted_count = transaction.execute( let maybe_row = transaction.query_opt(
" "
DELETE FROM post_reaction DELETE FROM post_reaction
WHERE author_id = $1 AND post_id = $2 WHERE author_id = $1 AND post_id = $2
RETURNING post_reaction.id
", ",
&[&author_id, &post_id], &[&author_id, &post_id],
).await?; ).await?;
if deleted_count == 0 { let row = maybe_row.ok_or(DatabaseError::NotFound("reaction"))?;
return Err(DatabaseError::NotFound("reaction")); let reaction_id = row.try_get("id")?;
}
update_reaction_count(&transaction, post_id, -1).await?; update_reaction_count(&transaction, post_id, -1).await?;
transaction.commit().await?; transaction.commit().await?;
Ok(()) Ok(reaction_id)
} }
/// Finds favourites among given posts and returns their IDs /// Finds favourites among given posts and returns their IDs