2022-12-07 22:49:05 +00:00
|
|
|
use serde::Deserialize;
|
2022-12-09 21:16:53 +00:00
|
|
|
use serde_json::Value;
|
2022-10-21 21:32:01 +00:00
|
|
|
use tokio_postgres::GenericClient;
|
|
|
|
|
|
|
|
use crate::activitypub::{
|
|
|
|
builders::{
|
|
|
|
follow::prepare_follow,
|
|
|
|
undo_follow::prepare_undo_follow,
|
|
|
|
},
|
|
|
|
fetcher::helpers::get_or_import_profile_by_actor_id,
|
2022-11-27 19:48:56 +00:00
|
|
|
identifiers::parse_local_actor_id,
|
2022-12-07 22:49:05 +00:00
|
|
|
receiver::parse_array,
|
2022-10-21 21:32:01 +00:00
|
|
|
vocabulary::PERSON,
|
|
|
|
};
|
|
|
|
use crate::config::Config;
|
2022-12-03 22:09:42 +00:00
|
|
|
use crate::database::DatabaseError;
|
|
|
|
use crate::errors::ValidationError;
|
2022-10-21 21:32:01 +00:00
|
|
|
use crate::models::{
|
2022-10-22 19:19:57 +00:00
|
|
|
notifications::queries::create_move_notification,
|
2022-11-26 23:19:13 +00:00
|
|
|
profiles::queries::search_profiles_by_did_only,
|
2022-10-21 21:32:01 +00:00
|
|
|
relationships::queries::{
|
|
|
|
create_follow_request,
|
|
|
|
get_followers,
|
|
|
|
unfollow,
|
|
|
|
},
|
2022-11-27 19:48:56 +00:00
|
|
|
users::queries::{get_user_by_id, get_user_by_name},
|
2022-10-21 21:32:01 +00:00
|
|
|
};
|
|
|
|
use super::HandlerResult;
|
|
|
|
|
2022-12-07 22:49:05 +00:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
struct Move {
|
|
|
|
actor: String,
|
|
|
|
object: String,
|
|
|
|
target: String,
|
|
|
|
}
|
|
|
|
|
2022-12-07 20:26:51 +00:00
|
|
|
pub async fn handle_move(
|
2022-10-21 21:32:01 +00:00
|
|
|
config: &Config,
|
|
|
|
db_client: &mut impl GenericClient,
|
2022-12-09 21:16:53 +00:00
|
|
|
activity: Value,
|
2022-10-21 21:32:01 +00:00
|
|
|
) -> HandlerResult {
|
2022-12-07 20:26:51 +00:00
|
|
|
// Move(Person)
|
2022-12-07 22:49:05 +00:00
|
|
|
let activity: Move = serde_json::from_value(activity)
|
2022-12-09 21:16:53 +00:00
|
|
|
.map_err(|_| ValidationError("unexpected activity structure"))?;
|
2022-12-07 22:49:05 +00:00
|
|
|
// Mastodon: actor is old profile (object)
|
|
|
|
// Mitra: actor is new profile (target)
|
|
|
|
if activity.object != activity.actor && activity.target != activity.actor {
|
2022-11-27 18:32:13 +00:00
|
|
|
return Err(ValidationError("actor ID mismatch").into());
|
|
|
|
};
|
|
|
|
|
2022-10-21 21:32:01 +00:00
|
|
|
let instance = config.instance();
|
|
|
|
let media_dir = config.media_dir();
|
2022-11-27 19:48:56 +00:00
|
|
|
let old_profile = if let Ok(username) = parse_local_actor_id(
|
|
|
|
&instance.url(),
|
2022-12-07 22:49:05 +00:00
|
|
|
&activity.object,
|
2022-11-27 19:48:56 +00:00
|
|
|
) {
|
|
|
|
let old_user = get_user_by_name(db_client, &username).await?;
|
|
|
|
old_user.profile
|
|
|
|
} else {
|
|
|
|
get_or_import_profile_by_actor_id(
|
|
|
|
db_client,
|
|
|
|
&instance,
|
|
|
|
&media_dir,
|
2022-12-07 22:49:05 +00:00
|
|
|
&activity.object,
|
2022-11-27 19:48:56 +00:00
|
|
|
).await?
|
|
|
|
};
|
|
|
|
let old_actor_id = old_profile.actor_id(&instance.url());
|
2022-10-21 21:32:01 +00:00
|
|
|
let new_profile = get_or_import_profile_by_actor_id(
|
|
|
|
db_client,
|
|
|
|
&instance,
|
|
|
|
&media_dir,
|
2022-12-07 22:49:05 +00:00
|
|
|
&activity.target,
|
2022-10-21 21:32:01 +00:00
|
|
|
).await?;
|
2022-12-07 22:49:05 +00:00
|
|
|
let new_actor = new_profile.actor_json
|
|
|
|
.expect("target should be a remote actor");
|
2022-11-26 23:19:13 +00:00
|
|
|
|
|
|
|
// Find aliases by DIDs
|
|
|
|
let mut aliases = vec![];
|
|
|
|
for identity_proof in new_profile.identity_proofs.inner() {
|
|
|
|
let profiles = search_profiles_by_did_only(
|
|
|
|
db_client,
|
|
|
|
&identity_proof.issuer,
|
|
|
|
).await?;
|
|
|
|
for profile in profiles {
|
|
|
|
if profile.id == new_profile.id {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
let actor_id = profile.actor_id(&instance.url());
|
|
|
|
aliases.push(actor_id);
|
|
|
|
};
|
|
|
|
};
|
|
|
|
// Read aliases from alsoKnownAs property
|
|
|
|
if let Some(ref value) = new_actor.also_known_as {
|
|
|
|
let also_known_as = parse_array(value)
|
|
|
|
.map_err(|_| ValidationError("invalid alias list"))?;
|
|
|
|
aliases.extend(also_known_as);
|
|
|
|
};
|
2022-11-27 22:03:45 +00:00
|
|
|
if !aliases.contains(&old_actor_id) {
|
2022-10-21 21:32:01 +00:00
|
|
|
return Err(ValidationError("target ID is not an alias").into());
|
|
|
|
};
|
|
|
|
|
|
|
|
let followers = get_followers(db_client, &old_profile.id).await?;
|
|
|
|
let mut activities = vec![];
|
|
|
|
for follower in followers {
|
|
|
|
let follower = get_user_by_id(db_client, &follower.id).await?;
|
|
|
|
// Unfollow old profile
|
|
|
|
let maybe_follow_request_id = unfollow(
|
|
|
|
db_client,
|
|
|
|
&follower.id,
|
|
|
|
&old_profile.id,
|
|
|
|
).await?;
|
2022-11-27 19:48:56 +00:00
|
|
|
// Send Undo(Follow) if old actor is not local
|
|
|
|
if let Some(ref old_actor) = old_profile.actor_json {
|
|
|
|
let follow_request_id = maybe_follow_request_id
|
|
|
|
.expect("follow request must exist");
|
|
|
|
activities.push(prepare_undo_follow(
|
|
|
|
&instance,
|
|
|
|
&follower,
|
|
|
|
old_actor,
|
|
|
|
&follow_request_id,
|
|
|
|
));
|
|
|
|
};
|
2022-10-21 21:32:01 +00:00
|
|
|
// Follow new profile
|
|
|
|
match create_follow_request(
|
|
|
|
db_client,
|
|
|
|
&follower.id,
|
|
|
|
&new_profile.id,
|
|
|
|
).await {
|
|
|
|
Ok(follow_request) => {
|
|
|
|
activities.push(prepare_follow(
|
|
|
|
&instance,
|
|
|
|
&follower,
|
|
|
|
&new_actor,
|
|
|
|
&follow_request.id,
|
|
|
|
));
|
|
|
|
},
|
|
|
|
Err(DatabaseError::AlreadyExists(_)) => (), // already following
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
2022-10-22 19:19:57 +00:00
|
|
|
create_move_notification(
|
|
|
|
db_client,
|
|
|
|
&new_profile.id,
|
|
|
|
&follower.id,
|
|
|
|
).await?;
|
2022-10-21 21:32:01 +00:00
|
|
|
};
|
|
|
|
tokio::spawn(async move {
|
|
|
|
for activity in activities {
|
|
|
|
activity.deliver_or_log().await;
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
Ok(Some(PERSON))
|
|
|
|
}
|