Rename /api/v1/accounts/move_followers to /api/v1/settings/move_followers
This commit is contained in:
parent
228299c5b7
commit
8d41a94b94
6 changed files with 163 additions and 143 deletions
|
@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added `/api/v1/settings/move_followers` API endpoint (replaces `/api/v1/accounts/move_followers`).
|
||||
|
||||
### Removed
|
||||
|
||||
- `/api/v1/accounts/move_followers` API endpoint.
|
||||
|
||||
## [1.9.0] - 2023-01-08
|
||||
|
||||
### Added
|
||||
|
|
|
@ -204,42 +204,6 @@ paths:
|
|||
description: Canonical representation of activity.
|
||||
type: string
|
||||
example: '{"type":"Update"}'
|
||||
/api/v1/accounts/move_followers:
|
||||
post:
|
||||
summary: Build Move(Person) activity for signing (experimental).
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
from_actor_id:
|
||||
description: The actor ID to move from.
|
||||
type: string
|
||||
example: 'https://xyz.com/users/test'
|
||||
followers_csv:
|
||||
description: The list of followers in CSV format.
|
||||
type: string
|
||||
example: |
|
||||
user1@example.org
|
||||
user2@test.com
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
params:
|
||||
description: Activity parameters
|
||||
$ref: '#/components/schemas/ActivityParameters'
|
||||
message:
|
||||
description: Canonical representation of activity.
|
||||
type: string
|
||||
example: '{"type":"Move"}'
|
||||
400:
|
||||
description: Invalid data.
|
||||
/api/v1/accounts/send_activity:
|
||||
post:
|
||||
summary: Send signed activity (experimental).
|
||||
|
@ -772,6 +736,42 @@ paths:
|
|||
example: |
|
||||
user1@example.org
|
||||
user2@example.org
|
||||
/api/v1/settings/move_followers:
|
||||
post:
|
||||
summary: Build Move(Person) activity for signing (experimental).
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
from_actor_id:
|
||||
description: The actor ID to move from.
|
||||
type: string
|
||||
example: 'https://xyz.com/users/test'
|
||||
followers_csv:
|
||||
description: The list of followers in CSV format.
|
||||
type: string
|
||||
example: |
|
||||
user1@example.org
|
||||
user2@test.com
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
params:
|
||||
description: Activity parameters
|
||||
$ref: '#/components/schemas/ActivityParameters'
|
||||
message:
|
||||
description: Canonical representation of activity.
|
||||
type: string
|
||||
example: '{"type":"Move"}'
|
||||
400:
|
||||
description: Invalid data.
|
||||
/api/v1/statuses:
|
||||
post:
|
||||
summary: Create new post.
|
||||
|
|
|
@ -305,12 +305,6 @@ impl AccountUpdateData {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MoveFollowersRequest {
|
||||
pub from_actor_id: String,
|
||||
pub followers_csv: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "kebab-case")]
|
||||
pub enum ActivityParams {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use actix_web::{
|
||||
get, patch, post, web,
|
||||
HttpRequest, HttpResponse, Scope,
|
||||
|
@ -7,11 +5,9 @@ use actix_web::{
|
|||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::activitypub::actors::types::ActorAddress;
|
||||
use crate::activitypub::builders::{
|
||||
follow::prepare_follow,
|
||||
move_person::{
|
||||
build_move_person,
|
||||
prepare_signed_move_person,
|
||||
},
|
||||
undo_follow::prepare_undo_follow,
|
||||
|
@ -50,7 +46,6 @@ use crate::mastodon_api::search::helpers::search_profiles_only;
|
|||
use crate::mastodon_api::statuses::helpers::build_status_list;
|
||||
use crate::mastodon_api::statuses::types::Status;
|
||||
use crate::models::posts::queries::get_posts_by_author;
|
||||
use crate::models::profiles::helpers::find_aliases;
|
||||
use crate::models::profiles::queries::{
|
||||
get_profile_by_acct,
|
||||
get_profile_by_id,
|
||||
|
@ -105,7 +100,6 @@ use super::types::{
|
|||
IdentityClaimQueryParams,
|
||||
IdentityProofData,
|
||||
LookupAcctQueryParams,
|
||||
MoveFollowersRequest,
|
||||
RelationshipQueryParams,
|
||||
SearchAcctQueryParams,
|
||||
SearchDidQueryParams,
|
||||
|
@ -266,94 +260,6 @@ async fn get_unsigned_update(
|
|||
Ok(HttpResponse::Ok().json(data))
|
||||
}
|
||||
|
||||
#[post("/move_followers")]
|
||||
async fn move_followers(
|
||||
auth: BearerAuth,
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<DbPool>,
|
||||
request_data: web::Json<MoveFollowersRequest>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||
// Existence of actor is not verified because
|
||||
// the old profile could have been deleted
|
||||
let maybe_from_profile = match get_profile_by_remote_actor_id(
|
||||
db_client,
|
||||
&request_data.from_actor_id,
|
||||
).await {
|
||||
Ok(profile) => Some(profile),
|
||||
Err(DatabaseError::NotFound(_)) => None,
|
||||
Err(other_error) => return Err(other_error.into()),
|
||||
};
|
||||
if maybe_from_profile.is_some() {
|
||||
// Find known aliases of the current user
|
||||
let mut aliases = find_aliases(db_client, ¤t_user.profile).await?
|
||||
.into_iter()
|
||||
.map(|profile| profile.actor_id(&config.instance_url()));
|
||||
if !aliases.any(|actor_id| actor_id == request_data.from_actor_id) {
|
||||
return Err(ValidationError("old profile is not an alias").into());
|
||||
};
|
||||
};
|
||||
let mut followers = vec![];
|
||||
for follower_address in request_data.followers_csv.lines() {
|
||||
let follower_acct = ActorAddress::from_str(follower_address)?
|
||||
.acct(&config.instance().hostname());
|
||||
// TODO: fetch unknown profiles
|
||||
let follower = get_profile_by_acct(db_client, &follower_acct).await?;
|
||||
if let Some(remote_actor) = follower.actor_json {
|
||||
// Add remote actor to activity recipients list
|
||||
followers.push(remote_actor.id);
|
||||
} 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
|
||||
let remote_actor = from_profile.actor_json.as_ref()
|
||||
.expect("actor data must be present");
|
||||
let follow_request_id = maybe_follow_request_id
|
||||
.expect("follow request must exist");
|
||||
prepare_undo_follow(
|
||||
&config.instance(),
|
||||
¤t_user,
|
||||
remote_actor,
|
||||
&follow_request_id,
|
||||
).enqueue(db_client).await?;
|
||||
},
|
||||
// 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()),
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let internal_activity_id = new_uuid();
|
||||
let activity = build_move_person(
|
||||
&config.instance_url(),
|
||||
¤t_user,
|
||||
&request_data.from_actor_id,
|
||||
&followers,
|
||||
&internal_activity_id,
|
||||
);
|
||||
let canonical_json = canonicalize_object(&activity)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
let data = UnsignedActivity {
|
||||
params: ActivityParams::Move {
|
||||
internal_activity_id,
|
||||
from_actor_id: request_data.from_actor_id.clone(),
|
||||
followers,
|
||||
},
|
||||
message: canonical_json,
|
||||
};
|
||||
Ok(HttpResponse::Ok().json(data))
|
||||
}
|
||||
|
||||
#[post("/send_activity")]
|
||||
async fn send_signed_activity(
|
||||
auth: BearerAuth,
|
||||
|
@ -850,7 +756,6 @@ pub fn account_api_scope() -> Scope {
|
|||
.service(verify_credentials)
|
||||
.service(update_credentials)
|
||||
.service(get_unsigned_update)
|
||||
.service(move_followers)
|
||||
.service(send_signed_activity)
|
||||
.service(get_identity_claim)
|
||||
.service(create_identity_proof)
|
||||
|
|
|
@ -4,3 +4,9 @@ use serde::Deserialize;
|
|||
pub struct PasswordChangeRequest {
|
||||
pub new_password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct MoveFollowersRequest {
|
||||
pub from_actor_id: String,
|
||||
pub followers_csv: String,
|
||||
}
|
||||
|
|
|
@ -1,17 +1,35 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use actix_web::{get, post, web, HttpResponse, Scope};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
|
||||
use crate::activitypub::{
|
||||
actors::types::ActorAddress,
|
||||
builders::{
|
||||
move_person::build_move_person,
|
||||
undo_follow::prepare_undo_follow,
|
||||
},
|
||||
};
|
||||
use crate::config::Config;
|
||||
use crate::database::{get_database_client, DbPool};
|
||||
use crate::errors::HttpError;
|
||||
use crate::database::{get_database_client, DatabaseError, DbPool};
|
||||
use crate::errors::{HttpError, ValidationError};
|
||||
use crate::mastodon_api::{
|
||||
accounts::types::Account,
|
||||
accounts::types::{Account, ActivityParams, UnsignedActivity},
|
||||
oauth::auth::get_current_user,
|
||||
};
|
||||
use crate::models::users::queries::set_user_password;
|
||||
use crate::utils::passwords::hash_password;
|
||||
use crate::models::{
|
||||
profiles::helpers::find_aliases,
|
||||
profiles::queries::{get_profile_by_acct, get_profile_by_remote_actor_id},
|
||||
relationships::queries::{follow, unfollow},
|
||||
users::queries::set_user_password,
|
||||
};
|
||||
use crate::utils::{
|
||||
canonicalization::canonicalize_object,
|
||||
id::new_uuid,
|
||||
passwords::hash_password,
|
||||
};
|
||||
use super::helpers::{export_followers, export_follows};
|
||||
use super::types::PasswordChangeRequest;
|
||||
use super::types::{MoveFollowersRequest, PasswordChangeRequest};
|
||||
|
||||
#[post("/change_password")]
|
||||
async fn change_password_view(
|
||||
|
@ -67,9 +85,98 @@ async fn export_follows_view(
|
|||
Ok(response)
|
||||
}
|
||||
|
||||
#[post("/move_followers")]
|
||||
async fn move_followers(
|
||||
auth: BearerAuth,
|
||||
config: web::Data<Config>,
|
||||
db_pool: web::Data<DbPool>,
|
||||
request_data: web::Json<MoveFollowersRequest>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let db_client = &mut **get_database_client(&db_pool).await?;
|
||||
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||
// Existence of actor is not verified because
|
||||
// the old profile could have been deleted
|
||||
let maybe_from_profile = match get_profile_by_remote_actor_id(
|
||||
db_client,
|
||||
&request_data.from_actor_id,
|
||||
).await {
|
||||
Ok(profile) => Some(profile),
|
||||
Err(DatabaseError::NotFound(_)) => None,
|
||||
Err(other_error) => return Err(other_error.into()),
|
||||
};
|
||||
if maybe_from_profile.is_some() {
|
||||
// Find known aliases of the current user
|
||||
let mut aliases = find_aliases(db_client, ¤t_user.profile).await?
|
||||
.into_iter()
|
||||
.map(|profile| profile.actor_id(&config.instance_url()));
|
||||
if !aliases.any(|actor_id| actor_id == request_data.from_actor_id) {
|
||||
return Err(ValidationError("old profile is not an alias").into());
|
||||
};
|
||||
};
|
||||
let mut followers = vec![];
|
||||
for follower_address in request_data.followers_csv.lines() {
|
||||
let follower_acct = ActorAddress::from_str(follower_address)?
|
||||
.acct(&config.instance().hostname());
|
||||
// TODO: fetch unknown profiles
|
||||
let follower = get_profile_by_acct(db_client, &follower_acct).await?;
|
||||
if let Some(remote_actor) = follower.actor_json {
|
||||
// Add remote actor to activity recipients list
|
||||
followers.push(remote_actor.id);
|
||||
} 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
|
||||
let remote_actor = from_profile.actor_json.as_ref()
|
||||
.expect("actor data must be present");
|
||||
let follow_request_id = maybe_follow_request_id
|
||||
.expect("follow request must exist");
|
||||
prepare_undo_follow(
|
||||
&config.instance(),
|
||||
¤t_user,
|
||||
remote_actor,
|
||||
&follow_request_id,
|
||||
).enqueue(db_client).await?;
|
||||
},
|
||||
// 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()),
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let internal_activity_id = new_uuid();
|
||||
let activity = build_move_person(
|
||||
&config.instance_url(),
|
||||
¤t_user,
|
||||
&request_data.from_actor_id,
|
||||
&followers,
|
||||
&internal_activity_id,
|
||||
);
|
||||
let canonical_json = canonicalize_object(&activity)
|
||||
.map_err(|_| HttpError::InternalError)?;
|
||||
let data = UnsignedActivity {
|
||||
params: ActivityParams::Move {
|
||||
internal_activity_id,
|
||||
from_actor_id: request_data.from_actor_id.clone(),
|
||||
followers,
|
||||
},
|
||||
message: canonical_json,
|
||||
};
|
||||
Ok(HttpResponse::Ok().json(data))
|
||||
}
|
||||
|
||||
pub fn settings_api_scope() -> Scope {
|
||||
web::scope("/api/v1/settings")
|
||||
.service(change_password_view)
|
||||
.service(export_followers_view)
|
||||
.service(export_follows_view)
|
||||
.service(move_followers)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue