Add /api/v1/settings/import_follows API endpoint
This commit is contained in:
parent
7218864563
commit
56df3d82a0
8 changed files with 155 additions and 34 deletions
|
@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Added `/api/v1/settings/move_followers` API endpoint (replaces `/api/v1/accounts/move_followers`).
|
- Added `/api/v1/settings/move_followers` API endpoint (replaces `/api/v1/accounts/move_followers`).
|
||||||
|
- Added `/api/v1/settings/import_follows` API endpoint.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
|
|
@ -736,6 +736,25 @@ paths:
|
||||||
example: |
|
example: |
|
||||||
user1@example.org
|
user1@example.org
|
||||||
user2@example.org
|
user2@example.org
|
||||||
|
/api/v1/settings/import_follows:
|
||||||
|
post:
|
||||||
|
summary: Import follows from CSV file.
|
||||||
|
security:
|
||||||
|
- tokenAuth: []
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
follows_csv:
|
||||||
|
description: The list of followers in CSV format.
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: Successful operation
|
||||||
|
400:
|
||||||
|
description: Invalid data.
|
||||||
/api/v1/settings/move_followers:
|
/api/v1/settings/move_followers:
|
||||||
post:
|
post:
|
||||||
summary: Move followers from remote alias.
|
summary: Move followers from remote alias.
|
||||||
|
|
|
@ -1,11 +1,55 @@
|
||||||
use tokio_postgres::GenericClient;
|
use tokio_postgres::GenericClient;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::activitypub::builders::follow::prepare_follow;
|
||||||
|
use crate::config::Instance;
|
||||||
use crate::database::DatabaseError;
|
use crate::database::DatabaseError;
|
||||||
use crate::models::relationships::queries::get_relationships;
|
use crate::models::{
|
||||||
use crate::models::relationships::types::RelationshipType;
|
profiles::types::DbActorProfile,
|
||||||
|
relationships::queries::{
|
||||||
|
create_follow_request,
|
||||||
|
follow,
|
||||||
|
get_relationships,
|
||||||
|
},
|
||||||
|
relationships::types::RelationshipType,
|
||||||
|
users::types::User,
|
||||||
|
};
|
||||||
use super::types::RelationshipMap;
|
use super::types::RelationshipMap;
|
||||||
|
|
||||||
|
pub async fn follow_or_create_request(
|
||||||
|
db_client: &mut impl GenericClient,
|
||||||
|
instance: &Instance,
|
||||||
|
current_user: &User,
|
||||||
|
target_profile: &DbActorProfile,
|
||||||
|
) -> Result<(), DatabaseError> {
|
||||||
|
if let Some(ref remote_actor) = target_profile.actor_json {
|
||||||
|
// Create follow request if target is remote
|
||||||
|
match create_follow_request(
|
||||||
|
db_client,
|
||||||
|
¤t_user.id,
|
||||||
|
&target_profile.id,
|
||||||
|
).await {
|
||||||
|
Ok(follow_request) => {
|
||||||
|
prepare_follow(
|
||||||
|
instance,
|
||||||
|
current_user,
|
||||||
|
remote_actor,
|
||||||
|
&follow_request.id,
|
||||||
|
).enqueue(db_client).await?;
|
||||||
|
},
|
||||||
|
Err(DatabaseError::AlreadyExists(_)) => (), // already following
|
||||||
|
Err(other_error) => return Err(other_error),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
match follow(db_client, ¤t_user.id, &target_profile.id).await {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(DatabaseError::AlreadyExists(_)) => (), // already following
|
||||||
|
Err(other_error) => return Err(other_error),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_relationship(
|
pub async fn get_relationship(
|
||||||
db_client: &impl GenericClient,
|
db_client: &impl GenericClient,
|
||||||
source_id: &Uuid,
|
source_id: &Uuid,
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
mod helpers;
|
pub mod helpers;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod views;
|
pub mod views;
|
||||||
|
|
|
@ -6,7 +6,6 @@ use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::activitypub::builders::{
|
use crate::activitypub::builders::{
|
||||||
follow::prepare_follow,
|
|
||||||
undo_follow::prepare_undo_follow,
|
undo_follow::prepare_undo_follow,
|
||||||
update_person::{
|
update_person::{
|
||||||
build_update_person,
|
build_update_person,
|
||||||
|
@ -55,8 +54,6 @@ use crate::models::profiles::types::{
|
||||||
ProofType,
|
ProofType,
|
||||||
};
|
};
|
||||||
use crate::models::relationships::queries::{
|
use crate::models::relationships::queries::{
|
||||||
create_follow_request,
|
|
||||||
follow,
|
|
||||||
get_followers_paginated,
|
get_followers_paginated,
|
||||||
get_following_paginated,
|
get_following_paginated,
|
||||||
hide_replies,
|
hide_replies,
|
||||||
|
@ -83,7 +80,7 @@ use crate::utils::{
|
||||||
id::new_uuid,
|
id::new_uuid,
|
||||||
passwords::hash_password,
|
passwords::hash_password,
|
||||||
};
|
};
|
||||||
use super::helpers::get_relationship;
|
use super::helpers::{follow_or_create_request, get_relationship};
|
||||||
use super::types::{
|
use super::types::{
|
||||||
Account,
|
Account,
|
||||||
AccountCreateData,
|
AccountCreateData,
|
||||||
|
@ -505,31 +502,12 @@ async fn follow_account(
|
||||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
let target = get_profile_by_id(db_client, &account_id).await?;
|
let target = get_profile_by_id(db_client, &account_id).await?;
|
||||||
if let Some(remote_actor) = target.actor_json {
|
follow_or_create_request(
|
||||||
// Create follow request if target is remote
|
db_client,
|
||||||
match create_follow_request(
|
&config.instance(),
|
||||||
db_client,
|
¤t_user,
|
||||||
¤t_user.id,
|
&target,
|
||||||
&target.id,
|
).await?;
|
||||||
).await {
|
|
||||||
Ok(follow_request) => {
|
|
||||||
prepare_follow(
|
|
||||||
&config.instance(),
|
|
||||||
¤t_user,
|
|
||||||
&remote_actor,
|
|
||||||
&follow_request.id,
|
|
||||||
).enqueue(db_client).await?;
|
|
||||||
},
|
|
||||||
Err(DatabaseError::AlreadyExists(_)) => (), // already following
|
|
||||||
Err(other_error) => return Err(other_error.into()),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
match follow(db_client, ¤t_user.id, &target.id).await {
|
|
||||||
Ok(_) => (),
|
|
||||||
Err(DatabaseError::AlreadyExists(_)) => (), // already following
|
|
||||||
Err(other_error) => return Err(other_error.into()),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
if follow_data.reblogs {
|
if follow_data.reblogs {
|
||||||
show_reposts(db_client, ¤t_user.id, &target.id).await?;
|
show_reposts(db_client, ¤t_user.id, &target.id).await?;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,12 +1,19 @@
|
||||||
use tokio_postgres::GenericClient;
|
use tokio_postgres::GenericClient;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::database::DatabaseError;
|
use crate::activitypub::{
|
||||||
|
fetcher::helpers::get_or_import_profile_by_actor_address,
|
||||||
|
HandlerError,
|
||||||
|
};
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::database::{get_database_client, DatabaseError, DbPool};
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
|
use crate::mastodon_api::accounts::helpers::follow_or_create_request;
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
profiles::types::DbActorProfile,
|
profiles::types::DbActorProfile,
|
||||||
posts::mentions::mention_to_address,
|
posts::mentions::mention_to_address,
|
||||||
relationships::queries::{get_followers, get_following},
|
relationships::queries::{get_followers, get_following},
|
||||||
|
users::types::User,
|
||||||
};
|
};
|
||||||
use crate::webfinger::types::ActorAddress;
|
use crate::webfinger::types::ActorAddress;
|
||||||
|
|
||||||
|
@ -58,6 +65,44 @@ pub fn parse_address_list(csv: &str)
|
||||||
Ok(addresses)
|
Ok(addresses)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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?;
|
||||||
|
for actor_address in address_list {
|
||||||
|
let profile = match get_or_import_profile_by_actor_address(
|
||||||
|
db_client,
|
||||||
|
&config.instance(),
|
||||||
|
&config.media_dir(),
|
||||||
|
&actor_address,
|
||||||
|
).await {
|
||||||
|
Ok(profile) => profile,
|
||||||
|
Err(error @ (
|
||||||
|
HandlerError::FetchError(_) |
|
||||||
|
HandlerError::DatabaseError(DatabaseError::NotFound(_))
|
||||||
|
)) => {
|
||||||
|
log::warn!(
|
||||||
|
"failed to import profile {}: {}",
|
||||||
|
actor_address,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
Err(other_error) => return Err(other_error.into()),
|
||||||
|
};
|
||||||
|
follow_or_create_request(
|
||||||
|
db_client,
|
||||||
|
&config.instance(),
|
||||||
|
¤t_user,
|
||||||
|
&profile,
|
||||||
|
).await?;
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::activitypub::actors::types::Actor;
|
use crate::activitypub::actors::types::Actor;
|
||||||
|
|
|
@ -5,6 +5,11 @@ pub struct PasswordChangeRequest {
|
||||||
pub new_password: String,
|
pub new_password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ImportFollowsRequest {
|
||||||
|
pub follows_csv: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct MoveFollowersRequest {
|
pub struct MoveFollowersRequest {
|
||||||
pub from_actor_id: String,
|
pub from_actor_id: String,
|
||||||
|
|
|
@ -24,9 +24,14 @@ use crate::utils::passwords::hash_password;
|
||||||
use super::helpers::{
|
use super::helpers::{
|
||||||
export_followers,
|
export_followers,
|
||||||
export_follows,
|
export_follows,
|
||||||
|
import_follows_task,
|
||||||
parse_address_list,
|
parse_address_list,
|
||||||
};
|
};
|
||||||
use super::types::{MoveFollowersRequest, PasswordChangeRequest};
|
use super::types::{
|
||||||
|
ImportFollowsRequest,
|
||||||
|
MoveFollowersRequest,
|
||||||
|
PasswordChangeRequest,
|
||||||
|
};
|
||||||
|
|
||||||
#[post("/change_password")]
|
#[post("/change_password")]
|
||||||
async fn change_password_view(
|
async fn change_password_view(
|
||||||
|
@ -82,6 +87,29 @@ async fn export_follows_view(
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/import_follows")]
|
||||||
|
async fn import_follows_view(
|
||||||
|
auth: BearerAuth,
|
||||||
|
config: web::Data<Config>,
|
||||||
|
db_pool: web::Data<DbPool>,
|
||||||
|
request_data: web::Json<ImportFollowsRequest>,
|
||||||
|
) -> Result<HttpResponse, HttpError> {
|
||||||
|
let db_client = &**get_database_client(&db_pool).await?;
|
||||||
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
|
let address_list = parse_address_list(&request_data.follows_csv)?;
|
||||||
|
tokio::spawn(async move {
|
||||||
|
import_follows_task(
|
||||||
|
&config,
|
||||||
|
current_user,
|
||||||
|
&db_pool,
|
||||||
|
address_list,
|
||||||
|
).await.unwrap_or_else(|error| {
|
||||||
|
log::error!("import follows: {}", error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
Ok(HttpResponse::NoContent().finish())
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/move_followers")]
|
#[post("/move_followers")]
|
||||||
async fn move_followers(
|
async fn move_followers(
|
||||||
auth: BearerAuth,
|
auth: BearerAuth,
|
||||||
|
@ -170,5 +198,6 @@ pub fn settings_api_scope() -> Scope {
|
||||||
.service(change_password_view)
|
.service(change_password_view)
|
||||||
.service(export_followers_view)
|
.service(export_followers_view)
|
||||||
.service(export_follows_view)
|
.service(export_follows_view)
|
||||||
|
.service(import_follows_view)
|
||||||
.service(move_followers)
|
.service(move_followers)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue