use serde::Deserialize; use serde_json::Value; use tokio_postgres::GenericClient; use crate::activitypub::{ fetcher::helpers::get_or_import_profile_by_actor_id, identifiers::parse_local_object_id, receiver::deserialize_into_object_id, vocabulary::NOTE, }; use crate::config::Config; use crate::database::DatabaseError; use crate::errors::ValidationError; use crate::models::reactions::queries::create_reaction; use crate::models::posts::queries::get_post_by_remote_object_id; use super::HandlerResult; #[derive(Deserialize)] struct Like { id: String, actor: String, #[serde(deserialize_with = "deserialize_into_object_id")] object: String, } pub async fn handle_like( config: &Config, db_client: &mut impl GenericClient, activity: Value, ) -> HandlerResult { let activity: Like = serde_json::from_value(activity) .map_err(|_| ValidationError("unexpected activity structure"))?; let author = get_or_import_profile_by_actor_id( db_client, &config.instance(), &config.media_dir(), &activity.actor, ).await?; let post_id = match parse_local_object_id( &config.instance_url(), &activity.object, ) { Ok(post_id) => post_id, Err(_) => { let post = match get_post_by_remote_object_id( db_client, &activity.object, ).await { Ok(post) => post, // Ignore like if post is not found locally Err(DatabaseError::NotFound(_)) => return Ok(None), Err(other_error) => return Err(other_error.into()), }; post.id }, }; match create_reaction( db_client, &author.id, &post_id, Some(&activity.id), ).await { Ok(_) => (), // Ignore activity if reaction is already saved Err(DatabaseError::AlreadyExists(_)) => return Ok(None), Err(other_error) => return Err(other_error.into()), }; Ok(Some(NOTE)) }