Allow calling follow/unfollow API methods multiple times

For compatibility with Mastodon.
This commit is contained in:
silverpill 2022-01-03 18:13:16 +00:00
parent d46165f397
commit 6d331f7669
3 changed files with 97 additions and 27 deletions

View file

@ -204,6 +204,38 @@ paths:
$ref: '#/components/schemas/Account' $ref: '#/components/schemas/Account'
404: 404:
description: Profile not found description: Profile not found
/api/v1/accounts/{account_id}/follow:
post:
summary: Follow the given actor.
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/account_id'
responses:
200:
description: Successfully followed, or actor was already followed
content:
application/json:
schema:
$ref: '#/components/schemas/Relationship'
404:
description: Profile not found
/api/v1/accounts/{account_id}/unfollow:
post:
summary: Unfollow the given actor.
security:
- tokenAuth: []
parameters:
- $ref: '#/components/parameters/account_id'
responses:
200:
description: Successfully unfollowed, or actor was already not followed
content:
application/json:
schema:
$ref: '#/components/schemas/Relationship'
404:
description: Profile not found
/api/v1/statuses/{status_id}: /api/v1/statuses/{status_id}:
delete: delete:
summary: Delete post summary: Delete post
@ -330,6 +362,10 @@ paths:
$ref: '#/components/schemas/Status' $ref: '#/components/schemas/Status'
components: components:
securitySchemes:
tokenAuth:
type: http
scheme: bearer
parameters: parameters:
account_id: account_id:
name: account_id name: account_id
@ -367,6 +403,22 @@ components:
description: Ethereum wallet address. description: Ethereum wallet address.
type: string type: string
example: '0xd8da6bf...' example: '0xd8da6bf...'
Relationship:
type: object
properties:
id:
description: The account id.
type: string
format: uuid
following:
description: Are you following this user?
type: boolean
followed_by:
description: Are you followed by this user?
type: boolean
requested:
description: Do you have a pending follow request for this user?
type: boolean
Status: Status:
type: object type: object
properties: properties:

View file

@ -12,7 +12,7 @@ use crate::activitypub::actor::Actor;
use crate::activitypub::deliverer::deliver_activity; use crate::activitypub::deliverer::deliver_activity;
use crate::config::Config; use crate::config::Config;
use crate::database::{Pool, get_database_client}; use crate::database::{Pool, get_database_client};
use crate::errors::{HttpError, ValidationError}; use crate::errors::{DatabaseError, HttpError, ValidationError};
use crate::ethereum::gate::is_allowed_user; use crate::ethereum::gate::is_allowed_user;
use crate::mastodon_api::oauth::auth::get_current_user; use crate::mastodon_api::oauth::auth::get_current_user;
use crate::models::posts::helpers::{ use crate::models::posts::helpers::{
@ -209,16 +209,25 @@ async fn follow_account(
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 { if let Some(remote_actor) = target.actor_json {
// Remote follow // Remote follow
let request = create_follow_request(db_client, &current_user.id, &target.id).await?; match create_follow_request(db_client, &current_user.id, &target.id).await {
let activity = create_activity_follow( Ok(request) => {
&config.instance_url(), let activity = create_activity_follow(
&current_user.profile, &config.instance_url(),
&request.id, &current_user.profile,
&remote_actor.id, &request.id,
); &remote_actor.id,
deliver_activity(&config, &current_user, activity, vec![remote_actor]); );
deliver_activity(&config, &current_user, activity, vec![remote_actor]);
},
Err(DatabaseError::AlreadyExists(_)) => (), // already following
Err(other_error) => return Err(other_error.into()),
};
} else { } else {
follow(db_client, &current_user.id, &target.id).await?; match follow(db_client, &current_user.id, &target.id).await {
Ok(_) => (),
Err(DatabaseError::AlreadyExists(_)) => (), // already following
Err(other_error) => return Err(other_error.into()),
};
}; };
let relationship = get_relationship( let relationship = get_relationship(
db_client, db_client,
@ -240,26 +249,35 @@ async fn unfollow_account(
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 { if let Some(remote_actor) = target.actor_json {
// Remote follow // Remote follow
let follow_request = get_follow_request_by_path( match get_follow_request_by_path(
db_client, db_client,
&current_user.id, &current_user.id,
&target.id, &target.id,
).await?; ).await {
unfollow( Ok(follow_request) => {
db_client, unfollow(
&current_user.id, db_client,
&target.id, &current_user.id,
).await?; &target.id,
// Federate ).await?;
let activity = create_activity_undo_follow( // Federate
&config.instance_url(), let activity = create_activity_undo_follow(
&current_user.profile, &config.instance_url(),
&follow_request.id, &current_user.profile,
&remote_actor.id, &follow_request.id,
); &remote_actor.id,
deliver_activity(&config, &current_user, activity, vec![remote_actor]); );
deliver_activity(&config, &current_user, activity, vec![remote_actor]);
},
Err(DatabaseError::NotFound(_)) => (), // not following
Err(other_error) => return Err(other_error.into()),
};
} else { } else {
unfollow(db_client, &current_user.id, &target.id).await?; match unfollow(db_client, &current_user.id, &target.id).await {
Ok(_) => (),
Err(DatabaseError::NotFound(_)) => (), // not following
Err(other_error) => return Err(other_error.into()),
};
}; };
let relationship = get_relationship( let relationship = get_relationship(
db_client, db_client,

View file

@ -161,7 +161,7 @@ pub async fn create_follow_request(
&request.target_id, &request.target_id,
&request.request_status, &request.request_status,
], ],
).await?; ).await.map_err(catch_unique_violation("follow request"))?;
Ok(request) Ok(request)
} }