Use /api/v1/subscriptions prefix for subscription API methods

This commit is contained in:
silverpill 2022-08-25 15:21:59 +00:00
parent 742e731b95
commit daaa0855a6
8 changed files with 142 additions and 117 deletions

View file

@ -148,43 +148,6 @@ paths:
$ref: '#/components/schemas/Account'
400:
description: Invalid proof data.
/api/v1/accounts/authorize_subscription:
get:
summary: Get authorization for setting up paid subscription.
parameters:
- name: price
in: query
description: Subscription price
required: true
schema:
type: number
responses:
200:
description: Signature created
content:
application/json:
schema:
$ref: '#/components/schemas/Signature'
403:
description: User's wallet address is not known or not verified
418:
description: Blockchain integration is not enabled
/api/v1/accounts/subscriptions_enabled:
post:
summary: Notify server about subscription setup
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
400:
description: User hasn't enabled subscriptions
403:
description: User's wallet address is not known or not verified
418:
description: Blockchain integration is not enabled
/api/v1/accounts/relationships:
get:
summary: Find out whether a given actor is followed, blocked, muted, etc.
@ -660,6 +623,43 @@ paths:
description: Post not found
422:
description: Transaction already registered
/api/v1/subscriptions/authorize:
get:
summary: Get authorization for setting up Ethereum subscription.
parameters:
- name: price
in: query
description: Subscription price
required: true
schema:
type: number
responses:
200:
description: Signature created
content:
application/json:
schema:
$ref: '#/components/schemas/Signature'
403:
description: User's wallet address is not known or not verified
418:
description: Blockchain integration is not enabled
/api/v1/subscriptions/enable:
post:
summary: Enable subscriptions
responses:
200:
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Account'
400:
description: User hasn't enabled subscriptions
403:
description: User's wallet address is not known or not verified
418:
description: Blockchain integration is not enabled
/api/v1/timelines/public:
get:
summary: View local public posts.

View file

@ -24,6 +24,7 @@ use mitra::mastodon_api::oauth::auth::create_auth_error_handler;
use mitra::mastodon_api::oauth::views::oauth_api_scope;
use mitra::mastodon_api::search::views::search_api_scope;
use mitra::mastodon_api::statuses::views::status_api_scope;
use mitra::mastodon_api::subscriptions::views::subscription_api_scope;
use mitra::mastodon_api::timelines::views::timeline_api_scope;
use mitra::mastodon_api::UPLOAD_MAX_SIZE;
use mitra::nodeinfo::views as nodeinfo;
@ -135,8 +136,9 @@ async fn main() -> std::io::Result<()> {
.service(marker_api_scope())
.service(media_api_scope())
.service(notification_api_scope())
.service(status_api_scope())
.service(search_api_scope())
.service(status_api_scope())
.service(subscription_api_scope())
.service(timeline_api_scope())
.service(webfinger::get_descriptor)
.service(activitypub::actor_scope())

View file

@ -251,11 +251,6 @@ pub struct IdentityProofData {
pub signature: String,
}
#[derive(Deserialize)]
pub struct SubscriptionQueryParams {
pub price: u64,
}
// TODO: actix currently doesn't support parameter arrays
// https://github.com/actix/actix-web/issues/2044
#[derive(Deserialize)]

View file

@ -22,14 +22,14 @@ use crate::ethereum::identity::{
create_identity_claim,
verify_identity_proof,
};
use crate::ethereum::subscriptions::{
create_subscription_signature,
is_registered_recipient,
};
use crate::mastodon_api::oauth::auth::get_current_user;
use crate::mastodon_api::pagination::get_paginated_response;
use crate::mastodon_api::statuses::helpers::build_status_list;
use crate::mastodon_api::statuses::types::Status;
use crate::mastodon_api::subscriptions::views::{
authorize_subscription,
subscriptions_enabled,
};
use crate::models::posts::queries::get_posts_by_author;
use crate::models::profiles::queries::{
get_profile_by_id,
@ -38,7 +38,6 @@ use crate::models::profiles::queries::{
};
use crate::models::profiles::types::{
IdentityProof,
PaymentOption,
ProfileUpdateData,
};
use crate::models::relationships::queries::{
@ -77,7 +76,6 @@ use super::types::{
RelationshipQueryParams,
SearchDidQueryParams,
StatusListQueryParams,
SubscriptionQueryParams,
ApiSubscription,
};
@ -295,72 +293,6 @@ async fn create_identity_proof(
Ok(HttpResponse::Ok().json(account))
}
#[get("/authorize_subscription")]
async fn authorize_subscription(
auth: BearerAuth,
config: web::Data<Config>,
db_pool: web::Data<Pool>,
query_params: web::Query<SubscriptionQueryParams>,
) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?;
let current_user = get_current_user(db_client, auth.token()).await?;
let ethereum_config = config.blockchain.as_ref()
.ok_or(HttpError::NotSupported)?
.ethereum_config()
.ok_or(HttpError::NotSupported)?;
// The user must have a public wallet address,
// because subscribers should be able
// to verify that payments are actually sent to the recipient.
let wallet_address = current_user
.public_wallet_address(&config.default_currency())
.ok_or(HttpError::PermissionError)?;
let signature = create_subscription_signature(
ethereum_config,
&wallet_address,
query_params.price,
).map_err(|_| HttpError::InternalError)?;
Ok(HttpResponse::Ok().json(signature))
}
#[post("/subscriptions_enabled")]
async fn subscriptions_enabled(
auth: BearerAuth,
config: web::Data<Config>,
db_pool: web::Data<Pool>,
maybe_blockchain: web::Data<Option<ContractSet>>,
) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?;
let mut current_user = get_current_user(db_client, auth.token()).await?;
let contract_set = maybe_blockchain.as_ref().as_ref()
.ok_or(HttpError::NotSupported)?;
let wallet_address = current_user
.public_wallet_address(&config.default_currency())
.ok_or(HttpError::PermissionError)?;
let is_registered = is_registered_recipient(contract_set, &wallet_address)
.await.map_err(|_| HttpError::InternalError)?;
if !is_registered {
return Err(ValidationError("recipient is not registered").into());
};
if current_user.profile.payment_options.is_empty() {
// Add payment option to profile
let mut profile_data = ProfileUpdateData::from(&current_user.profile);
profile_data.payment_options = vec![PaymentOption::EthereumSubscription];
current_user.profile = update_profile(
db_client,
&current_user.id,
profile_data,
).await?;
// Federate
prepare_update_person(db_client, config.instance(), &current_user)
.await?.spawn_deliver();
};
let account = Account::from_user(current_user, &config.instance_url());
Ok(HttpResponse::Ok().json(account))
}
#[get("/relationships")]
async fn get_relationships_view(
auth: BearerAuth,
@ -640,10 +572,10 @@ pub fn account_api_scope() -> Scope {
.service(update_credentials)
.service(get_identity_claim)
.service(create_identity_proof)
.service(authorize_subscription)
.service(subscriptions_enabled)
.service(get_relationships_view)
.service(search_by_did)
.route("/authorize_subscription", web::get().to(authorize_subscription))
.route("/subscriptions_enabled", web::post().to(subscriptions_enabled))
// Routes with account ID
.service(get_account)
.service(follow_account)

View file

@ -8,6 +8,7 @@ pub mod oauth;
mod pagination;
pub mod search;
pub mod statuses;
pub mod subscriptions;
pub mod timelines;
mod uploads;

View file

@ -0,0 +1,2 @@
mod types;
pub mod views;

View file

@ -0,0 +1,6 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct SubscriptionQueryParams {
pub price: u64,
}

View file

@ -0,0 +1,87 @@
use actix_web::{web, HttpResponse, Scope};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use crate::activitypub::builders::update_person::prepare_update_person;
use crate::config::Config;
use crate::database::{Pool, get_database_client};
use crate::errors::{HttpError, ValidationError};
use crate::ethereum::contracts::ContractSet;
use crate::ethereum::subscriptions::{
create_subscription_signature,
is_registered_recipient,
};
use crate::mastodon_api::accounts::types::Account;
use crate::mastodon_api::oauth::auth::get_current_user;
use crate::models::profiles::queries::update_profile;
use crate::models::profiles::types::{PaymentOption, ProfileUpdateData};
use super::types::SubscriptionQueryParams;
pub async fn authorize_subscription(
auth: BearerAuth,
config: web::Data<Config>,
db_pool: web::Data<Pool>,
query_params: web::Query<SubscriptionQueryParams>,
) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?;
let current_user = get_current_user(db_client, auth.token()).await?;
let ethereum_config = config.blockchain.as_ref()
.ok_or(HttpError::NotSupported)?
.ethereum_config()
.ok_or(HttpError::NotSupported)?;
// The user must have a public wallet address,
// because subscribers should be able
// to verify that payments are actually sent to the recipient.
let wallet_address = current_user
.public_wallet_address(&config.default_currency())
.ok_or(HttpError::PermissionError)?;
let signature = create_subscription_signature(
ethereum_config,
&wallet_address,
query_params.price,
).map_err(|_| HttpError::InternalError)?;
Ok(HttpResponse::Ok().json(signature))
}
pub async fn subscriptions_enabled(
auth: BearerAuth,
config: web::Data<Config>,
db_pool: web::Data<Pool>,
maybe_blockchain: web::Data<Option<ContractSet>>,
) -> Result<HttpResponse, HttpError> {
let db_client = &**get_database_client(&db_pool).await?;
let mut current_user = get_current_user(db_client, auth.token()).await?;
let contract_set = maybe_blockchain.as_ref().as_ref()
.ok_or(HttpError::NotSupported)?;
let wallet_address = current_user
.public_wallet_address(&config.default_currency())
.ok_or(HttpError::PermissionError)?;
let is_registered = is_registered_recipient(contract_set, &wallet_address)
.await.map_err(|_| HttpError::InternalError)?;
if !is_registered {
return Err(ValidationError("recipient is not registered").into());
};
if current_user.profile.payment_options.is_empty() {
// Add payment option to profile
let mut profile_data = ProfileUpdateData::from(&current_user.profile);
profile_data.payment_options = vec![PaymentOption::EthereumSubscription];
current_user.profile = update_profile(
db_client,
&current_user.id,
profile_data,
).await?;
// Federate
prepare_update_person(db_client, config.instance(), &current_user)
.await?.spawn_deliver();
};
let account = Account::from_user(current_user, &config.instance_url());
Ok(HttpResponse::Ok().json(account))
}
pub fn subscription_api_scope() -> Scope {
web::scope("/api/v1/subscriptions")
.route("/authorize", web::get().to(authorize_subscription))
.route("/enable", web::post().to(subscriptions_enabled))
}