2022-11-24 01:30:23 +00:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
2023-04-25 13:49:35 +00:00
|
|
|
use fedimovies_config::Config;
|
|
|
|
use fedimovies_models::{
|
2023-04-24 15:35:32 +00:00
|
|
|
database::{get_database_client, DatabaseClient, DatabaseError, DbPool},
|
2023-03-30 20:27:17 +00:00
|
|
|
profiles::types::DbActorProfile,
|
2023-04-24 15:35:32 +00:00
|
|
|
relationships::queries::{follow, get_followers, get_following, unfollow},
|
2023-03-30 20:27:17 +00:00
|
|
|
users::types::User,
|
|
|
|
};
|
2023-02-18 23:52:48 +00:00
|
|
|
|
2023-01-10 20:46:57 +00:00
|
|
|
use crate::activitypub::{
|
2023-02-26 19:03:48 +00:00
|
|
|
builders::{
|
2023-04-24 15:35:32 +00:00
|
|
|
follow::follow_or_create_request, move_person::prepare_move_person,
|
2023-02-26 19:03:48 +00:00
|
|
|
undo_follow::prepare_undo_follow,
|
|
|
|
},
|
2023-01-10 20:46:57 +00:00
|
|
|
fetcher::helpers::get_or_import_profile_by_actor_address,
|
|
|
|
HandlerError,
|
|
|
|
};
|
2023-01-10 01:36:06 +00:00
|
|
|
use crate::errors::ValidationError;
|
2023-03-25 21:50:10 +00:00
|
|
|
use crate::media::MediaStorage;
|
2023-01-10 01:36:06 +00:00
|
|
|
use crate::webfinger::types::ActorAddress;
|
2022-11-24 01:30:23 +00:00
|
|
|
|
2023-04-24 15:35:32 +00:00
|
|
|
fn export_profiles_to_csv(local_hostname: &str, profiles: Vec<DbActorProfile>) -> String {
|
2022-11-24 01:30:23 +00:00
|
|
|
let mut csv = String::new();
|
|
|
|
for profile in profiles {
|
2023-04-24 15:35:32 +00:00
|
|
|
let actor_address = ActorAddress::from_profile(local_hostname, &profile);
|
2022-11-24 01:30:23 +00:00
|
|
|
csv += &format!("{}\n", actor_address);
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2022-11-24 01:30:23 +00:00
|
|
|
csv
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn export_followers(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2022-11-24 01:30:23 +00:00
|
|
|
local_hostname: &str,
|
|
|
|
user_id: &Uuid,
|
|
|
|
) -> Result<String, DatabaseError> {
|
|
|
|
let followers = get_followers(db_client, user_id).await?;
|
|
|
|
let csv = export_profiles_to_csv(local_hostname, followers);
|
|
|
|
Ok(csv)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn export_follows(
|
2023-01-17 23:14:18 +00:00
|
|
|
db_client: &impl DatabaseClient,
|
2022-11-24 01:30:23 +00:00
|
|
|
local_hostname: &str,
|
|
|
|
user_id: &Uuid,
|
|
|
|
) -> Result<String, DatabaseError> {
|
|
|
|
let following = get_following(db_client, user_id).await?;
|
|
|
|
let csv = export_profiles_to_csv(local_hostname, following);
|
|
|
|
Ok(csv)
|
|
|
|
}
|
|
|
|
|
2023-04-24 15:35:32 +00:00
|
|
|
pub fn parse_address_list(csv: &str) -> Result<Vec<ActorAddress>, ValidationError> {
|
|
|
|
let mut addresses: Vec<_> = csv
|
|
|
|
.lines()
|
2023-01-10 01:36:06 +00:00
|
|
|
.map(|line| line.trim().to_string())
|
|
|
|
.filter(|line| !line.is_empty())
|
2023-03-23 19:19:16 +00:00
|
|
|
.map(|line| ActorAddress::from_mention(&line))
|
2023-01-10 01:36:06 +00:00
|
|
|
.collect::<Result<_, _>>()?;
|
|
|
|
addresses.sort();
|
|
|
|
addresses.dedup();
|
|
|
|
if addresses.len() > 50 {
|
2023-04-26 10:55:42 +00:00
|
|
|
return Err(ValidationError(
|
|
|
|
"can't process more than 50 items at once".to_string(),
|
|
|
|
));
|
2023-01-10 01:36:06 +00:00
|
|
|
};
|
|
|
|
Ok(addresses)
|
|
|
|
}
|
|
|
|
|
2023-01-10 20:46:57 +00:00
|
|
|
pub async fn import_follows_task(
|
|
|
|
config: &Config,
|
|
|
|
current_user: User,
|
|
|
|
db_pool: &DbPool,
|
|
|
|
address_list: Vec<ActorAddress>,
|
|
|
|
) -> Result<(), anyhow::Error> {
|
|
|
|
let db_client = &mut **get_database_client(db_pool).await?;
|
2023-03-25 21:50:10 +00:00
|
|
|
let storage = MediaStorage::from(config);
|
2023-01-10 20:46:57 +00:00
|
|
|
for actor_address in address_list {
|
|
|
|
let profile = match get_or_import_profile_by_actor_address(
|
|
|
|
db_client,
|
|
|
|
&config.instance(),
|
2023-03-25 21:50:10 +00:00
|
|
|
&storage,
|
2023-01-10 20:46:57 +00:00
|
|
|
&actor_address,
|
2023-04-24 15:35:32 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
{
|
2023-01-10 20:46:57 +00:00
|
|
|
Ok(profile) => profile,
|
2023-04-24 15:35:32 +00:00
|
|
|
Err(
|
|
|
|
error @ (HandlerError::FetchError(_)
|
|
|
|
| HandlerError::DatabaseError(DatabaseError::NotFound(_))),
|
|
|
|
) => {
|
|
|
|
log::warn!("failed to import profile {}: {}", actor_address, error,);
|
2023-01-10 20:46:57 +00:00
|
|
|
continue;
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2023-01-10 20:46:57 +00:00
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
2023-04-24 15:35:32 +00:00
|
|
|
follow_or_create_request(db_client, &config.instance(), ¤t_user, &profile).await?;
|
|
|
|
}
|
2023-01-10 20:46:57 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2023-02-26 19:03:48 +00:00
|
|
|
pub async fn move_followers_task(
|
|
|
|
config: &Config,
|
|
|
|
db_pool: &DbPool,
|
|
|
|
current_user: User,
|
|
|
|
from_actor_id: &str,
|
|
|
|
maybe_from_profile: Option<DbActorProfile>,
|
|
|
|
address_list: Vec<ActorAddress>,
|
|
|
|
) -> Result<(), anyhow::Error> {
|
|
|
|
let db_client = &mut **get_database_client(db_pool).await?;
|
|
|
|
let instance = config.instance();
|
2023-03-25 21:50:10 +00:00
|
|
|
let storage = MediaStorage::from(config);
|
2023-02-26 20:15:51 +00:00
|
|
|
let mut remote_followers = vec![];
|
2023-02-26 19:03:48 +00:00
|
|
|
for follower_address in address_list {
|
2023-02-26 20:15:51 +00:00
|
|
|
let follower = match get_or_import_profile_by_actor_address(
|
|
|
|
db_client,
|
|
|
|
&instance,
|
2023-03-25 21:50:10 +00:00
|
|
|
&storage,
|
2023-02-26 20:15:51 +00:00
|
|
|
&follower_address,
|
2023-04-24 15:35:32 +00:00
|
|
|
)
|
|
|
|
.await
|
|
|
|
{
|
2023-02-26 20:15:51 +00:00
|
|
|
Ok(profile) => profile,
|
2023-04-24 15:35:32 +00:00
|
|
|
Err(
|
|
|
|
error @ (HandlerError::FetchError(_)
|
|
|
|
| HandlerError::DatabaseError(DatabaseError::NotFound(_))),
|
|
|
|
) => {
|
|
|
|
log::warn!("failed to import profile {}: {}", follower_address, error,);
|
2023-02-26 20:15:51 +00:00
|
|
|
continue;
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2023-02-26 20:15:51 +00:00
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
2023-02-26 19:03:48 +00:00
|
|
|
if let Some(remote_actor) = follower.actor_json {
|
|
|
|
// Add remote actor to activity recipients list
|
2023-02-26 20:15:51 +00:00
|
|
|
remote_followers.push(remote_actor);
|
2023-02-26 19:03:48 +00:00
|
|
|
} else {
|
|
|
|
// Immediately move local followers (only if alias can be verified)
|
|
|
|
if let Some(ref from_profile) = maybe_from_profile {
|
|
|
|
match unfollow(db_client, &follower.id, &from_profile.id).await {
|
|
|
|
Ok(maybe_follow_request_id) => {
|
|
|
|
// Send Undo(Follow) to a remote actor
|
2023-04-24 15:35:32 +00:00
|
|
|
let remote_actor = from_profile
|
|
|
|
.actor_json
|
|
|
|
.as_ref()
|
2023-02-26 19:03:48 +00:00
|
|
|
.expect("actor data must be present");
|
2023-04-24 15:35:32 +00:00
|
|
|
let follow_request_id =
|
|
|
|
maybe_follow_request_id.expect("follow request must exist");
|
2023-02-26 19:03:48 +00:00
|
|
|
prepare_undo_follow(
|
|
|
|
&instance,
|
|
|
|
¤t_user,
|
|
|
|
remote_actor,
|
|
|
|
&follow_request_id,
|
2023-04-24 15:35:32 +00:00
|
|
|
)
|
|
|
|
.enqueue(db_client)
|
|
|
|
.await?;
|
|
|
|
}
|
2023-02-26 19:03:48 +00:00
|
|
|
// Not a follower, ignore
|
|
|
|
Err(DatabaseError::NotFound(_)) => continue,
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
|
|
|
match follow(db_client, &follower.id, ¤t_user.id).await {
|
|
|
|
Ok(_) => (),
|
|
|
|
// Ignore if already following
|
|
|
|
Err(DatabaseError::AlreadyExists(_)) => (),
|
|
|
|
Err(other_error) => return Err(other_error.into()),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
};
|
2023-04-24 15:35:32 +00:00
|
|
|
}
|
2023-02-26 19:03:48 +00:00
|
|
|
prepare_move_person(
|
|
|
|
&instance,
|
|
|
|
¤t_user,
|
|
|
|
from_actor_id,
|
2023-02-26 20:15:51 +00:00
|
|
|
remote_followers,
|
2023-02-26 19:03:48 +00:00
|
|
|
None,
|
2023-04-24 15:35:32 +00:00
|
|
|
)
|
|
|
|
.enqueue(db_client)
|
|
|
|
.await?;
|
2023-02-26 19:03:48 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2022-11-24 01:30:23 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2023-04-25 13:49:35 +00:00
|
|
|
use fedimovies_models::profiles::types::DbActor;
|
2022-11-24 01:30:23 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_export_profiles_to_csv() {
|
|
|
|
let profile_1 = DbActorProfile {
|
|
|
|
username: "user1".to_string(),
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
let profile_2 = DbActorProfile {
|
|
|
|
username: "user2".to_string(),
|
|
|
|
hostname: Some("test.net".to_string()),
|
2023-03-16 20:09:13 +00:00
|
|
|
actor_json: Some(DbActor::default()),
|
2022-11-24 01:30:23 +00:00
|
|
|
..Default::default()
|
|
|
|
};
|
2023-04-24 15:35:32 +00:00
|
|
|
let csv = export_profiles_to_csv("example.org", vec![profile_1, profile_2]);
|
2022-11-24 01:30:23 +00:00
|
|
|
assert_eq!(csv, "user1@example.org\nuser2@test.net\n");
|
|
|
|
}
|
2023-01-10 01:36:06 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_parse_address_list() {
|
|
|
|
let csv = concat!(
|
|
|
|
"\nuser1@example.net\n",
|
|
|
|
"user2@example.com \n",
|
|
|
|
"@user1@example.net",
|
|
|
|
);
|
|
|
|
let addresses = parse_address_list(csv).unwrap();
|
|
|
|
assert_eq!(addresses.len(), 2);
|
2023-04-24 15:35:32 +00:00
|
|
|
let addresses: Vec<_> = addresses
|
|
|
|
.into_iter()
|
2023-01-10 01:36:06 +00:00
|
|
|
.map(|address| address.to_string())
|
|
|
|
.collect();
|
2023-04-24 15:35:32 +00:00
|
|
|
assert_eq!(addresses, vec!["user1@example.net", "user2@example.com",]);
|
2023-01-10 01:36:06 +00:00
|
|
|
}
|
2022-11-24 01:30:23 +00:00
|
|
|
}
|