Add API method for listing incoming subscriptions
This commit is contained in:
parent
bdcdb06c51
commit
1721eb4a88
6 changed files with 154 additions and 2 deletions
|
@ -342,6 +342,36 @@ paths:
|
||||||
example: '<https://example.org/api/v1/accounts/1/following?limit=40&max_id=7628164>; rel="next"'
|
example: '<https://example.org/api/v1/accounts/1/following?limit=40&max_id=7628164>; rel="next"'
|
||||||
404:
|
404:
|
||||||
description: Profile not found
|
description: Profile not found
|
||||||
|
/api/v1/accounts/{account_id}/subscribers:
|
||||||
|
get:
|
||||||
|
summary: Subscriptions to the given actor.
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/account_id'
|
||||||
|
- name: max_id
|
||||||
|
in: query
|
||||||
|
description: Return results with subscription 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: Subscription list
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Subscription'
|
||||||
|
404:
|
||||||
|
description: Profile not found
|
||||||
/api/v1/accounts/{account_id}/follow:
|
/api/v1/accounts/{account_id}/follow:
|
||||||
post:
|
post:
|
||||||
summary: Follow the given actor.
|
summary: Follow the given actor.
|
||||||
|
@ -997,6 +1027,18 @@ components:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
example: '0x5fe80cdea7f...'
|
example: '0x5fe80cdea7f...'
|
||||||
|
Subscription:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: Subscription ID.
|
||||||
|
type: number
|
||||||
|
sender:
|
||||||
|
$ref: '#/components/schemas/Account'
|
||||||
|
sender_address:
|
||||||
|
description: Sender address.
|
||||||
|
type: string
|
||||||
|
example: '0xd8da6bf...'
|
||||||
Tag:
|
Tag:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -15,6 +15,7 @@ use crate::models::profiles::types::{
|
||||||
ProfileUpdateData,
|
ProfileUpdateData,
|
||||||
};
|
};
|
||||||
use crate::models::profiles::validators::validate_username;
|
use crate::models::profiles::validators::validate_username;
|
||||||
|
use crate::models::subscriptions::types::Subscription;
|
||||||
use crate::models::users::types::{
|
use crate::models::users::types::{
|
||||||
validate_local_username,
|
validate_local_username,
|
||||||
User,
|
User,
|
||||||
|
@ -334,6 +335,27 @@ pub struct FollowListQueryParams {
|
||||||
pub limit: u8,
|
pub limit: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct ApiSubscription {
|
||||||
|
pub id: i32,
|
||||||
|
pub sender: Account,
|
||||||
|
pub sender_address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApiSubscription {
|
||||||
|
pub fn from_subscription(
|
||||||
|
instance_url: &str,
|
||||||
|
subscription: Subscription,
|
||||||
|
) -> Self {
|
||||||
|
let sender = Account::from_profile(subscription.sender, instance_url);
|
||||||
|
Self {
|
||||||
|
id: subscription.id,
|
||||||
|
sender,
|
||||||
|
sender_address: subscription.sender_address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -52,6 +52,7 @@ use crate::models::relationships::queries::{
|
||||||
show_reposts,
|
show_reposts,
|
||||||
unfollow,
|
unfollow,
|
||||||
};
|
};
|
||||||
|
use crate::models::subscriptions::queries::get_incoming_subscriptions;
|
||||||
use crate::models::users::queries::{
|
use crate::models::users::queries::{
|
||||||
is_valid_invite_code,
|
is_valid_invite_code,
|
||||||
create_user,
|
create_user,
|
||||||
|
@ -76,6 +77,7 @@ use super::types::{
|
||||||
SearchDidQueryParams,
|
SearchDidQueryParams,
|
||||||
StatusListQueryParams,
|
StatusListQueryParams,
|
||||||
SubscriptionQueryParams,
|
SubscriptionQueryParams,
|
||||||
|
ApiSubscription,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[post("")]
|
#[post("")]
|
||||||
|
@ -584,6 +586,36 @@ async fn get_account_following(
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/{account_id}/subscribers")]
|
||||||
|
async fn get_account_subscribers(
|
||||||
|
auth: BearerAuth,
|
||||||
|
config: web::Data<Config>,
|
||||||
|
db_pool: web::Data<Pool>,
|
||||||
|
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 subscriptions: Vec<ApiSubscription> = vec![];
|
||||||
|
return Ok(HttpResponse::Ok().json(subscriptions));
|
||||||
|
};
|
||||||
|
let instance_url = config.instance_url();
|
||||||
|
let subscriptions: Vec<ApiSubscription> = get_incoming_subscriptions(
|
||||||
|
db_client,
|
||||||
|
&profile.id,
|
||||||
|
query_params.max_id,
|
||||||
|
query_params.limit.into(),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|item| ApiSubscription::from_subscription(&instance_url, item))
|
||||||
|
.collect();
|
||||||
|
Ok(HttpResponse::Ok().json(subscriptions))
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
@ -603,4 +635,5 @@ pub fn account_api_scope() -> Scope {
|
||||||
.service(get_account_statuses)
|
.service(get_account_statuses)
|
||||||
.service(get_account_followers)
|
.service(get_account_followers)
|
||||||
.service(get_account_following)
|
.service(get_account_following)
|
||||||
|
.service(get_account_subscribers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
pub mod queries;
|
pub mod queries;
|
||||||
mod types;
|
pub mod types;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use tokio_postgres::GenericClient;
|
use tokio_postgres::GenericClient;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -6,7 +8,7 @@ use crate::database::catch_unique_violation;
|
||||||
use crate::errors::DatabaseError;
|
use crate::errors::DatabaseError;
|
||||||
use crate::models::relationships::queries::{subscribe, subscribe_opt};
|
use crate::models::relationships::queries::{subscribe, subscribe_opt};
|
||||||
use crate::models::relationships::types::RelationshipType;
|
use crate::models::relationships::types::RelationshipType;
|
||||||
use super::types::DbSubscription;
|
use super::types::{DbSubscription, Subscription};
|
||||||
|
|
||||||
pub async fn create_subscription(
|
pub async fn create_subscription(
|
||||||
db_client: &mut impl GenericClient,
|
db_client: &mut impl GenericClient,
|
||||||
|
@ -111,3 +113,29 @@ pub async fn get_expired_subscriptions(
|
||||||
.collect::<Result<_, _>>()?;
|
.collect::<Result<_, _>>()?;
|
||||||
Ok(subscriptions)
|
Ok(subscriptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_incoming_subscriptions(
|
||||||
|
db_client: &impl GenericClient,
|
||||||
|
recipient_id: &Uuid,
|
||||||
|
max_subscription_id: Option<i32>,
|
||||||
|
limit: i64,
|
||||||
|
) -> Result<Vec<Subscription>, DatabaseError> {
|
||||||
|
let rows = db_client.query(
|
||||||
|
"
|
||||||
|
SELECT subscription, actor_profile AS sender
|
||||||
|
FROM actor_profile
|
||||||
|
JOIN subscription
|
||||||
|
ON (actor_profile.id = subscription.sender_id)
|
||||||
|
WHERE
|
||||||
|
subscription.recipient_id = $1
|
||||||
|
AND ($2::integer IS NULL OR subscription.id < $2)
|
||||||
|
ORDER BY subscription.id DESC
|
||||||
|
LIMIT $3
|
||||||
|
",
|
||||||
|
&[&recipient_id, &max_subscription_id, &limit],
|
||||||
|
).await?;
|
||||||
|
let subscriptions = rows.iter()
|
||||||
|
.map(Subscription::try_from)
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
Ok(subscriptions)
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use postgres_types::FromSql;
|
use postgres_types::FromSql;
|
||||||
|
use tokio_postgres::Row;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::errors::DatabaseError;
|
||||||
|
use crate::models::profiles::types::DbActorProfile;
|
||||||
|
|
||||||
#[derive(FromSql)]
|
#[derive(FromSql)]
|
||||||
#[postgres(name = "subscription")]
|
#[postgres(name = "subscription")]
|
||||||
pub struct DbSubscription {
|
pub struct DbSubscription {
|
||||||
|
@ -12,3 +18,24 @@ pub struct DbSubscription {
|
||||||
pub expires_at: DateTime<Utc>,
|
pub expires_at: DateTime<Utc>,
|
||||||
pub updated_at: DateTime<Utc>,
|
pub updated_at: DateTime<Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Subscription {
|
||||||
|
pub id: i32,
|
||||||
|
pub sender: DbActorProfile,
|
||||||
|
pub sender_address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Row> for Subscription {
|
||||||
|
|
||||||
|
type Error = DatabaseError;
|
||||||
|
|
||||||
|
fn try_from(row: &Row) -> Result<Self, Self::Error> {
|
||||||
|
let db_subscription: DbSubscription = row.try_get("subscription")?;
|
||||||
|
let db_sender: DbActorProfile = row.try_get("sender")?;
|
||||||
|
Ok(Self {
|
||||||
|
id: db_subscription.id,
|
||||||
|
sender: db_sender,
|
||||||
|
sender_address: db_subscription.sender_address,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue