Add API methods for retrieving followers and following lists
This commit is contained in:
parent
40958500c1
commit
3993c76c65
6 changed files with 175 additions and 7 deletions
|
@ -118,6 +118,9 @@ PATCH /api/v1/accounts/update_credentials
|
||||||
GET /api/v1/accounts/relationships
|
GET /api/v1/accounts/relationships
|
||||||
POST /api/v1/accounts/{account_id}/follow
|
POST /api/v1/accounts/{account_id}/follow
|
||||||
POST /api/v1/accounts/{account_id}/unfollow
|
POST /api/v1/accounts/{account_id}/unfollow
|
||||||
|
GET /api/v1/accounts/{account_id}/statuses
|
||||||
|
GET /api/v1/accounts/{account_id}/followers
|
||||||
|
GET /api/v1/accounts/{account_id}/following
|
||||||
GET /api/v1/directory
|
GET /api/v1/directory
|
||||||
GET /api/v1/instance
|
GET /api/v1/instance
|
||||||
GET /api/v1/markers
|
GET /api/v1/markers
|
||||||
|
@ -145,7 +148,7 @@ GET /api/v1/statuses/{status_id}/signature
|
||||||
POST /api/v1/statuses/{status_id}/token_minted
|
POST /api/v1/statuses/{status_id}/token_minted
|
||||||
```
|
```
|
||||||
|
|
||||||
[OpenAPI spec](./docs/openapi.yaml)
|
[OpenAPI spec](./docs/openapi.yaml) (incomplete)
|
||||||
|
|
||||||
## CLI commands
|
## CLI commands
|
||||||
|
|
||||||
|
|
|
@ -144,6 +144,66 @@ paths:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: '#/components/schemas/Status'
|
$ref: '#/components/schemas/Status'
|
||||||
|
/api/v1/accounts/{account_id}/followers:
|
||||||
|
get:
|
||||||
|
summary: Actors which follow the given actor.
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/account_id'
|
||||||
|
- name: max_id
|
||||||
|
in: query
|
||||||
|
description: Return results with relationship ID older than this value.
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: Maximum number of results to return.
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 40
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful operation
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
description: Profile list
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Account'
|
||||||
|
404:
|
||||||
|
description: Profile not found
|
||||||
|
/api/v1/accounts/{account_id}/following:
|
||||||
|
get:
|
||||||
|
summary: Actors which the given actor is following.
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/account_id'
|
||||||
|
- name: max_id
|
||||||
|
in: query
|
||||||
|
description: Return results with relationship ID older than this value.
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
description: Maximum number of results to return.
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 40
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Successful operation
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
description: Profile list
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Account'
|
||||||
|
404:
|
||||||
|
description: Profile not found
|
||||||
/api/v1/statuses/{status_id}:
|
/api/v1/statuses/{status_id}:
|
||||||
delete:
|
delete:
|
||||||
summary: Delete post
|
summary: Delete post
|
||||||
|
|
|
@ -174,6 +174,16 @@ impl AccountUpdateData {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_page_size() -> i64 { 40 }
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct FollowListQueryParams {
|
||||||
|
pub max_id: Option<i32>,
|
||||||
|
|
||||||
|
#[serde(default = "default_page_size")]
|
||||||
|
pub limit: i64,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -31,6 +31,7 @@ use crate::models::relationships::queries::{
|
||||||
follow,
|
follow,
|
||||||
get_follow_request_by_path,
|
get_follow_request_by_path,
|
||||||
get_followers,
|
get_followers,
|
||||||
|
get_following,
|
||||||
get_relationship,
|
get_relationship,
|
||||||
get_relationships,
|
get_relationships,
|
||||||
unfollow,
|
unfollow,
|
||||||
|
@ -45,7 +46,12 @@ use crate::utils::crypto::{
|
||||||
serialize_private_key,
|
serialize_private_key,
|
||||||
};
|
};
|
||||||
use crate::utils::files::FileError;
|
use crate::utils::files::FileError;
|
||||||
use super::types::{Account, AccountCreateData, AccountUpdateData};
|
use super::types::{
|
||||||
|
Account,
|
||||||
|
AccountCreateData,
|
||||||
|
AccountUpdateData,
|
||||||
|
FollowListQueryParams,
|
||||||
|
};
|
||||||
|
|
||||||
#[post("")]
|
#[post("")]
|
||||||
pub async fn create_account(
|
pub async fn create_account(
|
||||||
|
@ -154,7 +160,7 @@ async fn update_credentials(
|
||||||
// Federate
|
// Federate
|
||||||
let activity = create_activity_update_person(¤t_user, &config.instance_url())
|
let activity = create_activity_update_person(¤t_user, &config.instance_url())
|
||||||
.map_err(|_| HttpError::InternalError)?;
|
.map_err(|_| HttpError::InternalError)?;
|
||||||
let followers = get_followers(db_client, ¤t_user.id).await?;
|
let followers = get_followers(db_client, ¤t_user.id, None, None).await?;
|
||||||
let mut recipients: Vec<Actor> = Vec::new();
|
let mut recipients: Vec<Actor> = Vec::new();
|
||||||
for follower in followers {
|
for follower in followers {
|
||||||
if let Some(remote_actor) = follower.actor_json {
|
if let Some(remote_actor) = follower.actor_json {
|
||||||
|
@ -298,6 +304,62 @@ async fn get_account_statuses(
|
||||||
Ok(HttpResponse::Ok().json(statuses))
|
Ok(HttpResponse::Ok().json(statuses))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/{account_id}/followers")]
|
||||||
|
async fn get_account_followers(
|
||||||
|
auth: BearerAuth,
|
||||||
|
config: web::Data<Config>,
|
||||||
|
db_pool: web::Data<Pool>,
|
||||||
|
web::Path(account_id): web::Path<Uuid>,
|
||||||
|
query_params: web::Query<FollowListQueryParams>,
|
||||||
|
) -> Result<HttpResponse, HttpError> {
|
||||||
|
let db_client = &**get_database_client(&db_pool).await?;
|
||||||
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
|
let profile = get_profile_by_id(db_client, &account_id).await?;
|
||||||
|
if profile.id != current_user.id {
|
||||||
|
// Social graph is hidden
|
||||||
|
let accounts: Vec<Account> = vec![];
|
||||||
|
return Ok(HttpResponse::Ok().json(accounts));
|
||||||
|
};
|
||||||
|
let followers = get_followers(
|
||||||
|
db_client,
|
||||||
|
&profile.id,
|
||||||
|
query_params.max_id,
|
||||||
|
Some(query_params.limit),
|
||||||
|
).await?;
|
||||||
|
let accounts: Vec<Account> = followers.into_iter()
|
||||||
|
.map(|profile| Account::from_profile(profile, &config.instance_url()))
|
||||||
|
.collect();
|
||||||
|
Ok(HttpResponse::Ok().json(accounts))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/{account_id}/following")]
|
||||||
|
async fn get_account_following(
|
||||||
|
auth: BearerAuth,
|
||||||
|
config: web::Data<Config>,
|
||||||
|
db_pool: web::Data<Pool>,
|
||||||
|
web::Path(account_id): web::Path<Uuid>,
|
||||||
|
query_params: web::Query<FollowListQueryParams>,
|
||||||
|
) -> Result<HttpResponse, HttpError> {
|
||||||
|
let db_client = &**get_database_client(&db_pool).await?;
|
||||||
|
let current_user = get_current_user(db_client, auth.token()).await?;
|
||||||
|
let profile = get_profile_by_id(db_client, &account_id).await?;
|
||||||
|
if profile.id != current_user.id {
|
||||||
|
// Social graph is hidden
|
||||||
|
let accounts: Vec<Account> = vec![];
|
||||||
|
return Ok(HttpResponse::Ok().json(accounts));
|
||||||
|
};
|
||||||
|
let following = get_following(
|
||||||
|
db_client,
|
||||||
|
&profile.id,
|
||||||
|
query_params.max_id,
|
||||||
|
Some(query_params.limit),
|
||||||
|
).await?;
|
||||||
|
let accounts: Vec<Account> = following.into_iter()
|
||||||
|
.map(|profile| Account::from_profile(profile, &config.instance_url()))
|
||||||
|
.collect();
|
||||||
|
Ok(HttpResponse::Ok().json(accounts))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn account_api_scope() -> Scope {
|
pub fn account_api_scope() -> Scope {
|
||||||
web::scope("/api/v1/accounts")
|
web::scope("/api/v1/accounts")
|
||||||
// Routes without account ID
|
// Routes without account ID
|
||||||
|
@ -310,4 +372,6 @@ pub fn account_api_scope() -> Scope {
|
||||||
.service(follow_account)
|
.service(follow_account)
|
||||||
.service(unfollow_account)
|
.service(unfollow_account)
|
||||||
.service(get_account_statuses)
|
.service(get_account_statuses)
|
||||||
|
.service(get_account_followers)
|
||||||
|
.service(get_account_following)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub async fn get_note_audience(
|
||||||
current_user: &User,
|
current_user: &User,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
) -> Result<Vec<Actor>, DatabaseError> {
|
) -> Result<Vec<Actor>, DatabaseError> {
|
||||||
let mut audience = get_followers(db_client, ¤t_user.id).await?;
|
let mut audience = get_followers(db_client, ¤t_user.id, None, None).await?;
|
||||||
if let Some(in_reply_to_id) = post.in_reply_to_id {
|
if let Some(in_reply_to_id) = post.in_reply_to_id {
|
||||||
// TODO: use post.in_reply_to ?
|
// TODO: use post.in_reply_to ?
|
||||||
let in_reply_to_author = get_post_author(db_client, &in_reply_to_id).await?;
|
let in_reply_to_author = get_post_author(db_client, &in_reply_to_id).await?;
|
||||||
|
@ -51,7 +51,7 @@ pub async fn get_announce_audience(
|
||||||
current_user: &User,
|
current_user: &User,
|
||||||
post: &Post,
|
post: &Post,
|
||||||
) -> Result<Audience, DatabaseError> {
|
) -> Result<Audience, DatabaseError> {
|
||||||
let followers = get_followers(db_client, ¤t_user.id).await?;
|
let followers = get_followers(db_client, ¤t_user.id, None, None).await?;
|
||||||
let mut recipients: Vec<Actor> = Vec::new();
|
let mut recipients: Vec<Actor> = Vec::new();
|
||||||
for profile in followers {
|
for profile in followers {
|
||||||
if let Some(remote_actor) = profile.actor_json {
|
if let Some(remote_actor) = profile.actor_json {
|
||||||
|
|
|
@ -242,6 +242,8 @@ pub async fn get_follow_request_by_path(
|
||||||
pub async fn get_followers(
|
pub async fn get_followers(
|
||||||
db_client: &impl GenericClient,
|
db_client: &impl GenericClient,
|
||||||
profile_id: &Uuid,
|
profile_id: &Uuid,
|
||||||
|
max_relationship_id: Option<i32>,
|
||||||
|
limit: Option<i64>,
|
||||||
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
||||||
let rows = db_client.query(
|
let rows = db_client.query(
|
||||||
"
|
"
|
||||||
|
@ -249,10 +251,39 @@ pub async fn get_followers(
|
||||||
FROM actor_profile
|
FROM actor_profile
|
||||||
JOIN relationship
|
JOIN relationship
|
||||||
ON (actor_profile.id = relationship.source_id)
|
ON (actor_profile.id = relationship.source_id)
|
||||||
WHERE relationship.target_id = $1
|
WHERE
|
||||||
|
relationship.target_id = $1
|
||||||
|
AND ($2::integer IS NULL OR relationship.id < $2)
|
||||||
ORDER BY relationship.id DESC
|
ORDER BY relationship.id DESC
|
||||||
|
LIMIT $3
|
||||||
",
|
",
|
||||||
&[&profile_id],
|
&[&profile_id, &max_relationship_id, &limit],
|
||||||
|
).await?;
|
||||||
|
let profiles = rows.iter()
|
||||||
|
.map(|row| row.try_get("actor_profile"))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
Ok(profiles)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_following(
|
||||||
|
db_client: &impl GenericClient,
|
||||||
|
profile_id: &Uuid,
|
||||||
|
max_relationship_id: Option<i32>,
|
||||||
|
limit: Option<i64>,
|
||||||
|
) -> Result<Vec<DbActorProfile>, DatabaseError> {
|
||||||
|
let rows = db_client.query(
|
||||||
|
"
|
||||||
|
SELECT actor_profile
|
||||||
|
FROM actor_profile
|
||||||
|
JOIN relationship
|
||||||
|
ON (actor_profile.id = relationship.target_id)
|
||||||
|
WHERE
|
||||||
|
relationship.source_id = $1
|
||||||
|
AND ($2::integer IS NULL OR relationship.id < $2)
|
||||||
|
ORDER BY relationship.id DESC
|
||||||
|
LIMIT $3
|
||||||
|
",
|
||||||
|
&[&profile_id, &max_relationship_id, &limit],
|
||||||
).await?;
|
).await?;
|
||||||
let profiles = rows.iter()
|
let profiles = rows.iter()
|
||||||
.map(|row| row.try_get("actor_profile"))
|
.map(|row| row.try_get("actor_profile"))
|
||||||
|
|
Loading…
Reference in a new issue