Make /api/v1/accounts/{account_id}/follow work with form-data

This commit is contained in:
silverpill 2023-04-15 12:14:43 +00:00 committed by Rafael Caricio
parent e2ea58d33a
commit 7471c03ed1
Signed by: rafaelcaricio
GPG key ID: 3C86DBCE8E93C947
2 changed files with 144 additions and 254 deletions

View file

@ -11,6 +11,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Ignore errors when importing activities from outbox. - Ignore errors when importing activities from outbox.
- Make activity limit in outbox fetcher adjustable. - Make activity limit in outbox fetcher adjustable.
### Fixed
- Make `/api/v1/accounts/{account_id}/follow` work with form-data.
## [1.21.0] - 2023-04-12 ## [1.21.0] - 2023-04-12
### Added ### Added

View file

@ -1,13 +1,4 @@
use actix_web::{ use actix_web::{dev::ConnectionInfo, get, patch, post, web, HttpRequest, HttpResponse, Scope};
dev::ConnectionInfo,
get,
patch,
post,
web,
HttpRequest,
HttpResponse,
Scope,
};
use actix_web_httpauth::extractors::bearer::BearerAuth; use actix_web_httpauth::extractors::bearer::BearerAuth;
use uuid::Uuid; use uuid::Uuid;
@ -17,40 +8,21 @@ use mitra_models::{
posts::queries::get_posts_by_author, posts::queries::get_posts_by_author,
profiles::helpers::find_verified_aliases, profiles::helpers::find_verified_aliases,
profiles::queries::{ profiles::queries::{
get_profile_by_acct, get_profile_by_acct, get_profile_by_id, search_profiles_by_did, update_profile,
get_profile_by_id,
search_profiles_by_did,
update_profile,
},
profiles::types::{
IdentityProof,
IdentityProofType,
ProfileUpdateData,
}, },
profiles::types::{IdentityProof, IdentityProofType, ProfileUpdateData},
relationships::queries::{ relationships::queries::{
get_followers_paginated, get_followers_paginated, get_following_paginated, hide_replies, hide_reposts, show_replies,
get_following_paginated, show_reposts, unfollow,
hide_replies,
hide_reposts,
show_replies,
show_reposts,
unfollow,
}, },
subscriptions::queries::get_incoming_subscriptions, subscriptions::queries::get_incoming_subscriptions,
users::queries::{ users::queries::{create_user, get_user_by_did, is_valid_invite_code},
create_user,
get_user_by_did,
is_valid_invite_code,
},
users::types::{Role, UserCreateData}, users::types::{Role, UserCreateData},
}; };
use mitra_utils::{ use mitra_utils::{
caip2::ChainId, caip2::ChainId,
canonicalization::canonicalize_object, canonicalization::canonicalize_object,
crypto_rsa::{ crypto_rsa::{generate_rsa_key, serialize_private_key},
generate_rsa_key,
serialize_private_key,
},
currencies::Currency, currencies::Currency,
did::Did, did::Did,
did_pkh::DidPkh, did_pkh::DidPkh,
@ -58,69 +30,37 @@ use mitra_utils::{
passwords::hash_password, passwords::hash_password,
}; };
use super::helpers::{get_aliases, get_relationship};
use super::types::{
Account, AccountCreateData, AccountUpdateData, ActivityParams, ApiSubscription, FollowData,
FollowListQueryParams, IdentityClaim, IdentityClaimQueryParams, IdentityProofData,
LookupAcctQueryParams, RelationshipQueryParams, SearchAcctQueryParams, SearchDidQueryParams,
SignedActivity, StatusListQueryParams, UnsignedActivity,
};
use crate::activitypub::{ use crate::activitypub::{
builders::{ builders::{
follow::follow_or_create_request, follow::follow_or_create_request,
undo_follow::prepare_undo_follow, undo_follow::prepare_undo_follow,
update_person::{ update_person::{build_update_person, prepare_update_person},
build_update_person,
prepare_update_person,
},
}, },
identifiers::local_actor_id, identifiers::local_actor_id,
}; };
use crate::errors::ValidationError; use crate::errors::ValidationError;
use crate::http::get_request_base_url; use crate::http::{get_request_base_url, FormOrJson};
use crate::identity::{ use crate::identity::{
claims::create_identity_claim, claims::create_identity_claim,
minisign::{ minisign::{minisign_key_to_did, parse_minisign_signature, verify_minisign_signature},
minisign_key_to_did,
parse_minisign_signature,
verify_minisign_signature,
},
}; };
use crate::json_signatures::{ use crate::json_signatures::{
create::{add_integrity_proof, IntegrityProof}, create::{add_integrity_proof, IntegrityProof},
verify::{ verify::{verify_ed25519_json_signature, verify_eip191_json_signature},
verify_ed25519_json_signature,
verify_eip191_json_signature,
},
}; };
use crate::mastodon_api::{ use crate::mastodon_api::{
errors::MastodonError, errors::MastodonError, oauth::auth::get_current_user, pagination::get_paginated_response,
oauth::auth::get_current_user, search::helpers::search_profiles_only, statuses::helpers::build_status_list,
pagination::get_paginated_response,
search::helpers::search_profiles_only,
statuses::helpers::build_status_list,
statuses::types::Status, statuses::types::Status,
}; };
use crate::validators::{ use crate::validators::{profiles::clean_profile_update_data, users::validate_local_username};
profiles::clean_profile_update_data,
users::validate_local_username,
};
use super::helpers::{
get_aliases,
get_relationship,
};
use super::types::{
Account,
AccountCreateData,
AccountUpdateData,
ActivityParams,
ApiSubscription,
FollowData,
FollowListQueryParams,
IdentityClaim,
IdentityClaimQueryParams,
IdentityProofData,
LookupAcctQueryParams,
RelationshipQueryParams,
SearchAcctQueryParams,
SearchDidQueryParams,
SignedActivity,
StatusListQueryParams,
UnsignedActivity,
};
#[post("")] #[post("")]
pub async fn create_account( pub async fn create_account(
@ -132,7 +72,9 @@ pub async fn create_account(
let db_client = &mut **get_database_client(&db_pool).await?; let db_client = &mut **get_database_client(&db_pool).await?;
// Validate // Validate
if config.registration.registration_type == RegistrationType::Invite { if config.registration.registration_type == RegistrationType::Invite {
let invite_code = account_data.invite_code.as_ref() let invite_code = account_data
.invite_code
.as_ref()
.ok_or(ValidationError("invite code is required"))?; .ok_or(ValidationError("invite code is required"))?;
if !is_valid_invite_code(db_client, invite_code).await? { if !is_valid_invite_code(db_client, invite_code).await? {
return Err(ValidationError("invalid invite code").into()); return Err(ValidationError("invalid invite code").into());
@ -144,8 +86,7 @@ pub async fn create_account(
return Err(ValidationError("password or EIP-4361 message is required").into()); return Err(ValidationError("password or EIP-4361 message is required").into());
}; };
let maybe_password_hash = if let Some(password) = account_data.password.as_ref() { let maybe_password_hash = if let Some(password) = account_data.password.as_ref() {
let password_hash = hash_password(password) let password_hash = hash_password(password).map_err(|_| MastodonError::InternalError)?;
.map_err(|_| MastodonError::InternalError)?;
Some(password_hash) Some(password_hash)
} else { } else {
None None
@ -156,11 +97,14 @@ pub async fn create_account(
Ok(Ok(private_key)) => private_key, Ok(Ok(private_key)) => private_key,
_ => return Err(MastodonError::InternalError), _ => return Err(MastodonError::InternalError),
}; };
let private_key_pem = serialize_private_key(&private_key) let private_key_pem =
.map_err(|_| MastodonError::InternalError)?; serialize_private_key(&private_key).map_err(|_| MastodonError::InternalError)?;
let AccountCreateData { username, invite_code, .. } = let AccountCreateData {
account_data.into_inner(); username,
invite_code,
..
} = account_data.into_inner();
let role = match config.registration.default_role { let role = match config.registration.default_role {
DefaultRole::NormalUser => Role::NormalUser, DefaultRole::NormalUser => Role::NormalUser,
DefaultRole::ReadOnlyUser => Role::ReadOnlyUser, DefaultRole::ReadOnlyUser => Role::ReadOnlyUser,
@ -175,8 +119,9 @@ pub async fn create_account(
}; };
let user = match create_user(db_client, user_data).await { let user = match create_user(db_client, user_data).await {
Ok(user) => user, Ok(user) => user,
Err(DatabaseError::AlreadyExists(_)) => Err(DatabaseError::AlreadyExists(_)) => {
return Err(ValidationError("user already exists").into()), return Err(ValidationError("user already exists").into())
}
Err(other_error) => return Err(other_error.into()), Err(other_error) => return Err(other_error.into()),
}; };
log::warn!("created user {}", user.id); log::warn!("created user {}", user.id);
@ -215,25 +160,17 @@ async fn update_credentials(
) -> Result<HttpResponse, MastodonError> { ) -> Result<HttpResponse, MastodonError> {
let db_client = &mut **get_database_client(&db_pool).await?; let db_client = &mut **get_database_client(&db_pool).await?;
let mut current_user = get_current_user(db_client, auth.token()).await?; let mut current_user = get_current_user(db_client, auth.token()).await?;
let mut profile_data = account_data.into_inner() let mut profile_data = account_data
.into_profile_data( .into_inner()
&current_user.profile, .into_profile_data(&current_user.profile, &config.media_dir())?;
&config.media_dir(),
)?;
clean_profile_update_data(&mut profile_data)?; clean_profile_update_data(&mut profile_data)?;
current_user.profile = update_profile( current_user.profile = update_profile(db_client, &current_user.id, profile_data).await?;
db_client,
&current_user.id,
profile_data,
).await?;
// Federate // Federate
prepare_update_person( prepare_update_person(db_client, &config.instance(), &current_user, None)
db_client, .await?
&config.instance(), .enqueue(db_client)
&current_user, .await?;
None,
).await?.enqueue(db_client).await?;
let account = Account::from_user( let account = Account::from_user(
&get_request_base_url(connection_info), &get_request_base_url(connection_info),
@ -256,11 +193,14 @@ async fn get_unsigned_update(
&config.instance_url(), &config.instance_url(),
&current_user, &current_user,
Some(internal_activity_id), Some(internal_activity_id),
).map_err(|_| MastodonError::InternalError)?; )
let canonical_json = canonicalize_object(&activity) .map_err(|_| MastodonError::InternalError)?;
.map_err(|_| MastodonError::InternalError)?; let canonical_json =
canonicalize_object(&activity).map_err(|_| MastodonError::InternalError)?;
let data = UnsignedActivity { let data = UnsignedActivity {
params: ActivityParams::Update { internal_activity_id }, params: ActivityParams::Update {
internal_activity_id,
},
message: canonical_json, message: canonical_json,
}; };
Ok(HttpResponse::Ok().json(data)) Ok(HttpResponse::Ok().json(data))
@ -276,20 +216,24 @@ async fn send_signed_activity(
) -> Result<HttpResponse, MastodonError> { ) -> Result<HttpResponse, MastodonError> {
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 signer = data.signer.parse::<Did>() let signer = data
.signer
.parse::<Did>()
.map_err(|_| ValidationError("invalid DID"))?; .map_err(|_| ValidationError("invalid DID"))?;
if !current_user.profile.identity_proofs.any(&signer) { if !current_user.profile.identity_proofs.any(&signer) {
return Err(ValidationError("unknown signer").into()); return Err(ValidationError("unknown signer").into());
}; };
let mut outgoing_activity = match &data.params { let mut outgoing_activity = match &data.params {
ActivityParams::Update { internal_activity_id } => { ActivityParams::Update {
prepare_update_person( internal_activity_id,
db_client, } => prepare_update_person(
&config.instance(), db_client,
&current_user, &config.instance(),
Some(*internal_activity_id), &current_user,
).await.map_err(|_| MastodonError::InternalError)? Some(*internal_activity_id),
}, )
.await
.map_err(|_| MastodonError::InternalError)?,
}; };
let canonical_json = canonicalize_object(&outgoing_activity.activity) let canonical_json = canonicalize_object(&outgoing_activity.activity)
.map_err(|_| MastodonError::InternalError)?; .map_err(|_| MastodonError::InternalError)?;
@ -300,14 +244,14 @@ async fn send_signed_activity(
verify_ed25519_json_signature(&signer, &canonical_json, &signature_bin) verify_ed25519_json_signature(&signer, &canonical_json, &signature_bin)
.map_err(|_| ValidationError("invalid signature"))?; .map_err(|_| ValidationError("invalid signature"))?;
IntegrityProof::jcs_ed25519(&signer, &signature_bin) IntegrityProof::jcs_ed25519(&signer, &signature_bin)
}, }
Did::Pkh(signer) => { Did::Pkh(signer) => {
let signature_bin = hex::decode(&data.signature) let signature_bin =
.map_err(|_| ValidationError("invalid encoding"))?; hex::decode(&data.signature).map_err(|_| ValidationError("invalid encoding"))?;
verify_eip191_json_signature(&signer, &canonical_json, &signature_bin) verify_eip191_json_signature(&signer, &canonical_json, &signature_bin)
.map_err(|_| ValidationError("invalid signature"))?; .map_err(|_| ValidationError("invalid signature"))?;
IntegrityProof::jcs_eip191(&signer, &signature_bin) IntegrityProof::jcs_eip191(&signer, &signature_bin)
}, }
}; };
add_integrity_proof(&mut outgoing_activity.activity, proof) add_integrity_proof(&mut outgoing_activity.activity, proof)
.map_err(|_| MastodonError::InternalError)?; .map_err(|_| MastodonError::InternalError)?;
@ -333,25 +277,18 @@ async fn get_identity_claim(
let current_user = get_current_user(db_client, auth.token()).await?; let current_user = get_current_user(db_client, auth.token()).await?;
let did = match query_params.proof_type.as_str() { let did = match query_params.proof_type.as_str() {
"ethereum" => { "ethereum" => {
let did_pkh = DidPkh::from_address( let did_pkh = DidPkh::from_address(&Currency::Ethereum, &query_params.signer);
&Currency::Ethereum,
&query_params.signer,
);
Did::Pkh(did_pkh) Did::Pkh(did_pkh)
}, }
"minisign" => { "minisign" => {
let did_key = minisign_key_to_did(&query_params.signer) let did_key = minisign_key_to_did(&query_params.signer)
.map_err(|_| ValidationError("invalid key"))?; .map_err(|_| ValidationError("invalid key"))?;
Did::Key(did_key) Did::Key(did_key)
}, }
_ => return Err(ValidationError("unknown proof type").into()), _ => return Err(ValidationError("unknown proof type").into()),
}; };
let actor_id = local_actor_id( let actor_id = local_actor_id(&config.instance_url(), &current_user.profile.username);
&config.instance_url(), let claim = create_identity_claim(&actor_id, &did).map_err(|_| MastodonError::InternalError)?;
&current_user.profile.username,
);
let claim = create_identity_claim(&actor_id, &did)
.map_err(|_| MastodonError::InternalError)?;
let response = IdentityClaim { did, claim }; let response = IdentityClaim { did, claim };
Ok(HttpResponse::Ok().json(response)) Ok(HttpResponse::Ok().json(response))
} }
@ -366,7 +303,9 @@ async fn create_identity_proof(
) -> Result<HttpResponse, MastodonError> { ) -> Result<HttpResponse, MastodonError> {
let db_client = &mut **get_database_client(&db_pool).await?; let db_client = &mut **get_database_client(&db_pool).await?;
let mut current_user = get_current_user(db_client, auth.token()).await?; let mut current_user = get_current_user(db_client, auth.token()).await?;
let did = proof_data.did.parse::<Did>() let did = proof_data
.did
.parse::<Did>()
.map_err(|_| ValidationError("invalid DID"))?; .map_err(|_| ValidationError("invalid DID"))?;
// Reject proof if there's another local user with the same DID. // Reject proof if there's another local user with the same DID.
// This is needed for matching ethereum subscriptions // This is needed for matching ethereum subscriptions
@ -375,37 +314,30 @@ async fn create_identity_proof(
if user.id != current_user.id { if user.id != current_user.id {
return Err(ValidationError("DID already associated with another user").into()); return Err(ValidationError("DID already associated with another user").into());
}; };
}, }
Err(DatabaseError::NotFound(_)) => (), Err(DatabaseError::NotFound(_)) => (),
Err(other_error) => return Err(other_error.into()), Err(other_error) => return Err(other_error.into()),
}; };
let actor_id = local_actor_id( let actor_id = local_actor_id(&config.instance_url(), &current_user.profile.username);
&config.instance_url(), let message =
&current_user.profile.username, create_identity_claim(&actor_id, &did).map_err(|_| ValidationError("invalid claim"))?;
);
let message = create_identity_claim(&actor_id, &did)
.map_err(|_| ValidationError("invalid claim"))?;
// Verify proof // Verify proof
let proof_type = match did { let proof_type = match did {
Did::Key(ref did_key) => { Did::Key(ref did_key) => {
let signature_bin = parse_minisign_signature(&proof_data.signature) let signature_bin = parse_minisign_signature(&proof_data.signature)
.map_err(|_| ValidationError("invalid signature encoding"))?; .map_err(|_| ValidationError("invalid signature encoding"))?;
verify_minisign_signature( verify_minisign_signature(did_key, &message, &signature_bin)
did_key, .map_err(|_| ValidationError("invalid signature"))?;
&message,
&signature_bin,
).map_err(|_| ValidationError("invalid signature"))?;
IdentityProofType::LegacyMinisignIdentityProof IdentityProofType::LegacyMinisignIdentityProof
}, }
Did::Pkh(ref did_pkh) => { Did::Pkh(ref did_pkh) => {
if did_pkh.chain_id != ChainId::ethereum_mainnet() { if did_pkh.chain_id != ChainId::ethereum_mainnet() {
// DID must point to Ethereum Mainnet because it is a valid // DID must point to Ethereum Mainnet because it is a valid
// identifier on any Ethereum chain // identifier on any Ethereum chain
return Err(ValidationError("unsupported chain ID").into()); return Err(ValidationError("unsupported chain ID").into());
}; };
let maybe_public_address = let maybe_public_address = current_user.public_wallet_address(&Currency::Ethereum);
current_user.public_wallet_address(&Currency::Ethereum);
if let Some(address) = maybe_public_address { if let Some(address) = maybe_public_address {
// Do not allow to add more than one address proof // Do not allow to add more than one address proof
if did_pkh.address != address { if did_pkh.address != address {
@ -413,7 +345,7 @@ async fn create_identity_proof(
}; };
}; };
return Err(ValidationError("invalid signature").into()); return Err(ValidationError("invalid signature").into());
}, }
}; };
let proof = IdentityProof { let proof = IdentityProof {
@ -423,19 +355,13 @@ async fn create_identity_proof(
}; };
let mut profile_data = ProfileUpdateData::from(&current_user.profile); let mut profile_data = ProfileUpdateData::from(&current_user.profile);
profile_data.add_identity_proof(proof); profile_data.add_identity_proof(proof);
current_user.profile = update_profile( current_user.profile = update_profile(db_client, &current_user.id, profile_data).await?;
db_client,
&current_user.id,
profile_data,
).await?;
// Federate // Federate
prepare_update_person( prepare_update_person(db_client, &config.instance(), &current_user, None)
db_client, .await?
&config.instance(), .enqueue(db_client)
&current_user, .await?;
None,
).await?.enqueue(db_client).await?;
let account = Account::from_user( let account = Account::from_user(
&get_request_base_url(connection_info), &get_request_base_url(connection_info),
@ -453,11 +379,7 @@ async fn get_relationships_view(
) -> Result<HttpResponse, MastodonError> { ) -> Result<HttpResponse, MastodonError> {
let db_client = &**get_database_client(&db_pool).await?; let db_client = &**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 relationship = get_relationship( let relationship = get_relationship(db_client, &current_user.id, &query_params.id).await?;
db_client,
&current_user.id,
&query_params.id,
).await?;
Ok(HttpResponse::Ok().json(vec![relationship])) Ok(HttpResponse::Ok().json(vec![relationship]))
} }
@ -490,13 +412,13 @@ async fn search_by_acct(
match auth { match auth {
Some(auth) => { Some(auth) => {
get_current_user(db_client, auth.token()).await?; get_current_user(db_client, auth.token()).await?;
}, }
None => { None => {
// Only authorized users can make webfinger queries // Only authorized users can make webfinger queries
if query_params.resolve { if query_params.resolve {
return Err(MastodonError::PermissionError); return Err(MastodonError::PermissionError);
}; };
}, }
}; };
let profiles = search_profiles_only( let profiles = search_profiles_only(
&config, &config,
@ -504,15 +426,13 @@ async fn search_by_acct(
&query_params.q, &query_params.q,
query_params.resolve, query_params.resolve,
query_params.limit.inner(), query_params.limit.inner(),
).await?; )
.await?;
let base_url = get_request_base_url(connection_info); let base_url = get_request_base_url(connection_info);
let instance_url = config.instance().url(); let instance_url = config.instance().url();
let accounts: Vec<Account> = profiles.into_iter() let accounts: Vec<Account> = profiles
.map(|profile| Account::from_profile( .into_iter()
&base_url, .map(|profile| Account::from_profile(&base_url, &instance_url, profile))
&instance_url,
profile,
))
.collect(); .collect();
Ok(HttpResponse::Ok().json(accounts)) Ok(HttpResponse::Ok().json(accounts))
} }
@ -525,17 +445,16 @@ async fn search_by_did(
query_params: web::Query<SearchDidQueryParams>, query_params: web::Query<SearchDidQueryParams>,
) -> Result<HttpResponse, MastodonError> { ) -> Result<HttpResponse, MastodonError> {
let db_client = &**get_database_client(&db_pool).await?; let db_client = &**get_database_client(&db_pool).await?;
let did: Did = query_params.did.parse() let did: Did = query_params
.did
.parse()
.map_err(|_| ValidationError("invalid DID"))?; .map_err(|_| ValidationError("invalid DID"))?;
let profiles = search_profiles_by_did(db_client, &did, false).await?; let profiles = search_profiles_by_did(db_client, &did, false).await?;
let base_url = get_request_base_url(connection_info); let base_url = get_request_base_url(connection_info);
let instance_url = config.instance().url(); let instance_url = config.instance().url();
let accounts: Vec<Account> = profiles.into_iter() let accounts: Vec<Account> = profiles
.map(|profile| Account::from_profile( .into_iter()
&base_url, .map(|profile| Account::from_profile(&base_url, &instance_url, profile))
&instance_url,
profile,
))
.collect(); .collect();
Ok(HttpResponse::Ok().json(accounts)) Ok(HttpResponse::Ok().json(accounts))
} }
@ -563,17 +482,13 @@ async fn follow_account(
config: web::Data<Config>, config: web::Data<Config>,
db_pool: web::Data<DbPool>, db_pool: web::Data<DbPool>,
account_id: web::Path<Uuid>, account_id: web::Path<Uuid>,
follow_data: web::Json<FollowData>, follow_data: FormOrJson<FollowData>,
) -> Result<HttpResponse, MastodonError> { ) -> Result<HttpResponse, MastodonError> {
let follow_data = follow_data.into_inner();
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?;
follow_or_create_request( follow_or_create_request(db_client, &config.instance(), &current_user, &target).await?;
db_client,
&config.instance(),
&current_user,
&target,
).await?;
if follow_data.reblogs { if follow_data.reblogs {
show_reposts(db_client, &current_user.id, &target.id).await?; show_reposts(db_client, &current_user.id, &target.id).await?;
} else { } else {
@ -584,11 +499,7 @@ async fn follow_account(
} else { } else {
hide_replies(db_client, &current_user.id, &target.id).await?; hide_replies(db_client, &current_user.id, &target.id).await?;
}; };
let relationship = get_relationship( let relationship = get_relationship(db_client, &current_user.id, &target.id).await?;
db_client,
&current_user.id,
&target.id,
).await?;
Ok(HttpResponse::Ok().json(relationship)) Ok(HttpResponse::Ok().json(relationship))
} }
@ -605,25 +516,22 @@ async fn unfollow_account(
match unfollow(db_client, &current_user.id, &target.id).await { match unfollow(db_client, &current_user.id, &target.id).await {
Ok(Some(follow_request_id)) => { Ok(Some(follow_request_id)) => {
// Remote follow // Remote follow
let remote_actor = target.actor_json let remote_actor = target.actor_json.ok_or(MastodonError::InternalError)?;
.ok_or(MastodonError::InternalError)?;
prepare_undo_follow( prepare_undo_follow(
&config.instance(), &config.instance(),
&current_user, &current_user,
&remote_actor, &remote_actor,
&follow_request_id, &follow_request_id,
).enqueue(db_client).await?; )
}, .enqueue(db_client)
Ok(None) => (), // local follow .await?;
}
Ok(None) => (), // local follow
Err(DatabaseError::NotFound(_)) => (), // not following Err(DatabaseError::NotFound(_)) => (), // not following
Err(other_error) => return Err(other_error.into()), Err(other_error) => return Err(other_error.into()),
}; };
let relationship = get_relationship( let relationship = get_relationship(db_client, &current_user.id, &target.id).await?;
db_client,
&current_user.id,
&target.id,
).await?;
Ok(HttpResponse::Ok().json(relationship)) Ok(HttpResponse::Ok().json(relationship))
} }
@ -656,14 +564,16 @@ async fn get_account_statuses(
true, true,
query_params.max_id, query_params.max_id,
query_params.limit.inner(), query_params.limit.inner(),
).await?; )
.await?;
let statuses = build_status_list( let statuses = build_status_list(
db_client, db_client,
&get_request_base_url(connection_info), &get_request_base_url(connection_info),
&config.instance_url(), &config.instance_url(),
maybe_current_user.as_ref(), maybe_current_user.as_ref(),
posts, posts,
).await?; )
.await?;
Ok(HttpResponse::Ok().json(statuses)) Ok(HttpResponse::Ok().json(statuses))
} }
@ -690,24 +600,18 @@ async fn get_account_followers(
&profile.id, &profile.id,
query_params.max_id, query_params.max_id,
query_params.limit.inner(), query_params.limit.inner(),
).await?; )
.await?;
let max_index = usize::from(query_params.limit.inner().saturating_sub(1)); let max_index = usize::from(query_params.limit.inner().saturating_sub(1));
let maybe_last_id = followers.get(max_index).map(|item| item.relationship_id); let maybe_last_id = followers.get(max_index).map(|item| item.relationship_id);
let base_url = get_request_base_url(connection_info); let base_url = get_request_base_url(connection_info);
let instance_url = config.instance().url(); let instance_url = config.instance().url();
let accounts: Vec<Account> = followers.into_iter() let accounts: Vec<Account> = followers
.map(|item| Account::from_profile( .into_iter()
&base_url, .map(|item| Account::from_profile(&base_url, &instance_url, item.profile))
&instance_url,
item.profile,
))
.collect(); .collect();
let response = get_paginated_response( let response =
&instance_url, get_paginated_response(&instance_url, request.uri().path(), accounts, maybe_last_id);
request.uri().path(),
accounts,
maybe_last_id,
);
Ok(response) Ok(response)
} }
@ -734,24 +638,18 @@ async fn get_account_following(
&profile.id, &profile.id,
query_params.max_id, query_params.max_id,
query_params.limit.inner(), query_params.limit.inner(),
).await?; )
.await?;
let max_index = usize::from(query_params.limit.inner().saturating_sub(1)); let max_index = usize::from(query_params.limit.inner().saturating_sub(1));
let maybe_last_id = following.get(max_index).map(|item| item.relationship_id); let maybe_last_id = following.get(max_index).map(|item| item.relationship_id);
let base_url = get_request_base_url(connection_info); let base_url = get_request_base_url(connection_info);
let instance_url = config.instance().url(); let instance_url = config.instance().url();
let accounts: Vec<Account> = following.into_iter() let accounts: Vec<Account> = following
.map(|item| Account::from_profile( .into_iter()
&base_url, .map(|item| Account::from_profile(&base_url, &instance_url, item.profile))
&instance_url,
item.profile,
))
.collect(); .collect();
let response = get_paginated_response( let response =
&instance_url, get_paginated_response(&instance_url, request.uri().path(), accounts, maybe_last_id);
request.uri().path(),
accounts,
maybe_last_id,
);
Ok(response) Ok(response)
} }
@ -780,14 +678,10 @@ async fn get_account_subscribers(
query_params.max_id, query_params.max_id,
query_params.limit.inner(), query_params.limit.inner(),
) )
.await? .await?
.into_iter() .into_iter()
.map(|subscription| ApiSubscription::from_subscription( .map(|subscription| ApiSubscription::from_subscription(&base_url, &instance_url, subscription))
&base_url, .collect();
&instance_url,
subscription,
))
.collect();
Ok(HttpResponse::Ok().json(subscriptions)) Ok(HttpResponse::Ok().json(subscriptions))
} }
@ -803,12 +697,9 @@ async fn get_account_aliases(
let aliases = find_verified_aliases(db_client, &profile).await?; let aliases = find_verified_aliases(db_client, &profile).await?;
let base_url = get_request_base_url(connection_info); let base_url = get_request_base_url(connection_info);
let instance_url = config.instance_url(); let instance_url = config.instance_url();
let accounts: Vec<Account> = aliases.into_iter() let accounts: Vec<Account> = aliases
.map(|profile| Account::from_profile( .into_iter()
&base_url, .map(|profile| Account::from_profile(&base_url, &instance_url, profile))
&instance_url,
profile,
))
.collect(); .collect();
Ok(HttpResponse::Ok().json(accounts)) Ok(HttpResponse::Ok().json(accounts))
} }
@ -824,12 +715,7 @@ async fn get_all_account_aliases(
let profile = get_profile_by_id(db_client, &account_id).await?; let profile = get_profile_by_id(db_client, &account_id).await?;
let base_url = get_request_base_url(connection_info); let base_url = get_request_base_url(connection_info);
let instance_url = config.instance_url(); let instance_url = config.instance_url();
let aliases = get_aliases( let aliases = get_aliases(db_client, &base_url, &instance_url, &profile).await?;
db_client,
&base_url,
&instance_url,
&profile,
).await?;
Ok(HttpResponse::Ok().json(aliases)) Ok(HttpResponse::Ok().json(aliases))
} }