diff --git a/docs/openapi.yaml b/docs/openapi.yaml index 4492e3c..3575ea1 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -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. diff --git a/src/main.rs b/src/main.rs index 66cc8e6..bc36be4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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()) diff --git a/src/mastodon_api/accounts/types.rs b/src/mastodon_api/accounts/types.rs index ddb82ae..cb20635 100644 --- a/src/mastodon_api/accounts/types.rs +++ b/src/mastodon_api/accounts/types.rs @@ -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)] diff --git a/src/mastodon_api/accounts/views.rs b/src/mastodon_api/accounts/views.rs index 483198f..ef33133 100644 --- a/src/mastodon_api/accounts/views.rs +++ b/src/mastodon_api/accounts/views.rs @@ -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, - db_pool: web::Data, - query_params: web::Query, -) -> Result { - 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, - db_pool: web::Data, - maybe_blockchain: web::Data>, -) -> Result { - 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(¤t_user.profile); - profile_data.payment_options = vec![PaymentOption::EthereumSubscription]; - current_user.profile = update_profile( - db_client, - ¤t_user.id, - profile_data, - ).await?; - - // Federate - prepare_update_person(db_client, config.instance(), ¤t_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) diff --git a/src/mastodon_api/mod.rs b/src/mastodon_api/mod.rs index 2df13f8..6105850 100644 --- a/src/mastodon_api/mod.rs +++ b/src/mastodon_api/mod.rs @@ -8,6 +8,7 @@ pub mod oauth; mod pagination; pub mod search; pub mod statuses; +pub mod subscriptions; pub mod timelines; mod uploads; diff --git a/src/mastodon_api/subscriptions/mod.rs b/src/mastodon_api/subscriptions/mod.rs new file mode 100644 index 0000000..718ba5f --- /dev/null +++ b/src/mastodon_api/subscriptions/mod.rs @@ -0,0 +1,2 @@ +mod types; +pub mod views; diff --git a/src/mastodon_api/subscriptions/types.rs b/src/mastodon_api/subscriptions/types.rs new file mode 100644 index 0000000..375f317 --- /dev/null +++ b/src/mastodon_api/subscriptions/types.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct SubscriptionQueryParams { + pub price: u64, +} diff --git a/src/mastodon_api/subscriptions/views.rs b/src/mastodon_api/subscriptions/views.rs new file mode 100644 index 0000000..64397dd --- /dev/null +++ b/src/mastodon_api/subscriptions/views.rs @@ -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, + db_pool: web::Data, + query_params: web::Query, +) -> Result { + 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, + db_pool: web::Data, + maybe_blockchain: web::Data>, +) -> Result { + 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(¤t_user.profile); + profile_data.payment_options = vec![PaymentOption::EthereumSubscription]; + current_user.profile = update_profile( + db_client, + ¤t_user.id, + profile_data, + ).await?; + + // Federate + prepare_update_person(db_client, config.instance(), ¤t_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)) +}