diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 39e656aa8..cfe683aad 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -20,6 +20,12 @@ postgres = ["bytes", "tokio-postgres", "deadpool-postgres"] # All Addresses and keystore files exist in the ganache-cli setup for testing with the EthereumAdapter test-util = [] +[[example]] +name = "accounting_response" + +[[example]] +name = "all_spenders_response" + [[example]] name = "analytics_query" @@ -45,11 +51,17 @@ name = "channel_last_approved_response" name = "channel_last_approved_query" [[example]] -name = "create_campaign" +name = "channel_pay_request" + +[[example]] +name = "create_campaign_request" required-features = ["test-util"] [[example]] -name = "modify_campaign" +name = "modify_campaign_request" + +[[example]] +name = "spender_response" [[example]] name = "validator_messages_create_request" diff --git a/primitives/examples/accounting_response.rs b/primitives/examples/accounting_response.rs new file mode 100644 index 000000000..728a6fce9 --- /dev/null +++ b/primitives/examples/accounting_response.rs @@ -0,0 +1,29 @@ +use primitives::{balances::CheckedState, sentry::AccountingResponse}; +use serde_json::{from_value, json}; + +fn main() { + // Empty balances + { + let json = json!({ + "earners": {}, + "spenders": {}, + }); + assert!(from_value::>(json).is_ok()); + } + + // Non-empty balances + { + // earners sum and spenders sum should always match since balances are CheckedState + let json = json!({ + "earners": { + "0x80690751969B234697e9059e04ed72195c3507fa": "10000000000", + "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7": "20000000000", + "0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9": "30000000000", + }, + "spenders": { + "0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F": "60000000000", + }, + }); + assert!(from_value::>(json).is_ok()); + } +} diff --git a/primitives/examples/all_spenders_response.rs b/primitives/examples/all_spenders_response.rs new file mode 100644 index 000000000..a525de6a5 --- /dev/null +++ b/primitives/examples/all_spenders_response.rs @@ -0,0 +1,24 @@ +use primitives::sentry::AllSpendersResponse; +use serde_json::{from_value, json}; + +fn main() { + let json = json!({ + "spenders": { + "0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F": { + "totalDeposited": "10000000000", + "totalSpent": "100000000", + }, + "0xDd589B43793934EF6Ad266067A0d1D4896b0dff0": { + "totalDeposited": "90000000000", + "totalSpent": "20000000000", + }, + "0x541b401362Ea1D489D322579552B099e801F3632": { + "totalDeposited": "1000000000", + "totalSpent": "1000000000", + }, + }, + "totalPages": 1, + "page": 0 + }); + assert!(from_value::(json).is_ok()); +} diff --git a/primitives/examples/channel_pay_request.rs b/primitives/examples/channel_pay_request.rs new file mode 100644 index 000000000..b875125be --- /dev/null +++ b/primitives/examples/channel_pay_request.rs @@ -0,0 +1,17 @@ +use primitives::sentry::ChannelPayRequest; +use serde_json::json; +use std::str::FromStr; + +fn main() { + let channel_pay_json = json!({ + "payouts": { + "0x80690751969B234697e9059e04ed72195c3507fa": "10000000000", + "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7": "20000000000", + "0x0e880972A4b216906F05D67EeaaF55d16B5EE4F1": "30000000000", + }, + }); + + let channel_pay_json = serde_json::to_string(&channel_pay_json).expect("should serialize"); + + assert!(serde_json::from_str::(&channel_pay_json).is_ok()); +} diff --git a/primitives/examples/create_campaign.rs b/primitives/examples/create_campaign_request.rs similarity index 100% rename from primitives/examples/create_campaign.rs rename to primitives/examples/create_campaign_request.rs diff --git a/primitives/examples/modify_campaign.rs b/primitives/examples/modify_campaign_request.rs similarity index 100% rename from primitives/examples/modify_campaign.rs rename to primitives/examples/modify_campaign_request.rs diff --git a/primitives/examples/spender_response.rs b/primitives/examples/spender_response.rs new file mode 100644 index 000000000..801ee8770 --- /dev/null +++ b/primitives/examples/spender_response.rs @@ -0,0 +1,12 @@ +use primitives::sentry::SpenderResponse; +use serde_json::{from_value, json}; + +fn main() { + let json = json!({ + "spender": { + "totalDeposited": "10000000000", + "totalSpent": "100000000", + }, + }); + assert!(from_value::(json).is_ok()); +} diff --git a/primitives/src/sentry.rs b/primitives/src/sentry.rs index 139a3158f..d61f93b0a 100644 --- a/primitives/src/sentry.rs +++ b/primitives/src/sentry.rs @@ -22,9 +22,15 @@ pub use event::{Event, EventType, CLICK, IMPRESSION}; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] -/// Channel Accounting response. +/// Channel Accounting response /// -/// A collection of all `Accounting`s for a specific [`Channel`](crate::Channel). +/// A collection of all `Accounting`s for a specific [`Channel`](`crate::Channel`) +/// +/// # Examples +/// +/// ``` +#[doc = include_str!("../examples/accounting_response.rs")] +/// ``` pub struct AccountingResponse { #[serde(flatten, bound = "S: BalancesState")] pub balances: Balances, @@ -628,12 +634,26 @@ pub struct SuccessResponse { pub success: bool, } +/// Spender limits for a spender on a `Channel`. +/// +/// # Examples +/// +/// ``` +#[doc = include_str!("../examples/spender_response.rs")] +/// ``` #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct SpenderResponse { pub spender: Spender, } +/// Spender limits for all spenders on a `Channel`. +/// +/// # Examples +/// +/// ``` +#[doc = include_str!("../examples/all_spenders_response.rs")] +/// ``` #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct AllSpendersResponse { @@ -649,6 +669,13 @@ pub struct AllSpendersQuery { pub page: u64, } +/// Payouts to be performed for the given [`Channel`](`crate::Channel`). +/// +/// # Examples +/// +/// ``` +#[doc = include_str!("../examples/channel_pay_request.rs")] +/// ``` #[derive(Debug, Serialize, Deserialize)] pub struct ChannelPayRequest { pub payouts: UnifiedMap, @@ -667,6 +694,13 @@ pub struct ValidatorMessagesListResponse { pub messages: Vec, } +/// Contains all the different validator messages to be created. +/// +/// # Examples +/// +/// ``` +#[doc = include_str!("../examples/validator_messages_create_request.rs")] +/// ``` #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct ValidatorMessagesCreateRequest { @@ -862,7 +896,7 @@ pub mod campaign_create { /// # Examples /// /// ``` - #[doc = include_str!("../examples/create_campaign.rs")] + #[doc = include_str!("../examples/create_campaign_request.rs")] /// ``` #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] @@ -955,7 +989,7 @@ pub mod campaign_modify { /// /// # Examples: /// ``` - #[doc = include_str!("../examples/modify_campaign.rs")] + #[doc = include_str!("../examples/modify_campaign_request.rs")] /// ``` #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ModifyCampaign { diff --git a/sentry/src/db/validator_message.rs b/sentry/src/db/validator_message.rs index d124f18c3..e3611688b 100644 --- a/sentry/src/db/validator_message.rs +++ b/sentry/src/db/validator_message.rs @@ -11,7 +11,7 @@ use primitives::{ use super::{DbPool, PoolError}; /// Inserts a new validator [`MessageTypes`] using the `from` [`ValidatorId`] and `received` at: [`Utc::now()`][Utc] -pub async fn insert_validator_messages( +pub async fn insert_validator_message( pool: &DbPool, channel: &Channel, from: &ValidatorId, diff --git a/sentry/src/routes.rs b/sentry/src/routes.rs index 32ea2fd06..a498ac004 100644 --- a/sentry/src/routes.rs +++ b/sentry/src/routes.rs @@ -59,15 +59,38 @@ //! //! #### GET `/v5/channel/:id/accounting` //! +//! Gets all of the accounting entries for a channel from the database and checks the balances. +//! //! The route is handled by [`channel::get_accounting_for_channel()`]. //! -//! Response: [`AccountingResponse::`](primitives::sentry::AccountingResponse) +//! Response: [`AccountingResponse`] +//! +//! ##### Examples +//! +//! Response: +//! +//! ``` +#![doc = include_str!("../../primitives/examples/accounting_response.rs")] +//! ``` //! //! #### GET `/v5/channel/:id/spender/:addr` (auth required) //! +//! Gets the spender limits for a spender on a [`Channel`]. It does so by fetching the +//! latest Spendable entry from the database (or creating one if it doesn't exist yet) from which +//! the total deposited amount is retrieved, and the latest NewState from which the total spent +//! amount is retrieved. +//! //! The route is handled by [`channel::get_spender_limits()`]. //! -//! Response: [`SpenderResponse`](primitives::sentry::SpenderResponse) +//! Response: [`SpenderResponse`] +//! +//! ##### Examples +//! +//! Response: +//! +//! ``` +#![doc = include_str!("../../primitives/examples/spender_response.rs")] +//! ``` //! //! #### POST `/v5/channel/:id/spender/:addr` (auth required) //! @@ -81,9 +104,19 @@ //! //! #### GET `/v5/channel/:id/spender/all` (auth required) //! +//! This routes gets total_deposited and total_spent for every spender on a [`Channel`] +//! //! The route is handled by [`channel::get_all_spender_limits()`]. //! -//! Response: [`AllSpendersResponse`](primitives::sentry::AllSpendersResponse) +//! Response: [`AllSpendersResponse`] +//! +//! ##### Examples +//! +//! Response: +//! +//! ``` +#![doc = include_str!("../../primitives/examples/all_spenders_response.rs")] +//! ``` //! //! #### GET `/v5/channel/:id/validator-messages` //! @@ -153,6 +186,13 @@ //! //! The same is true of the [`Heartbeat`]s messages if they are requested with the query parameter. //! +//! Retrieves the latest [`ApproveState`] and the corresponding [`NewState`] +//! validator messages for the given [`Channel`]. +//! +//! If the [`Channel`] is new one or both of the states might have not been generated yet. +//! +//! The same is true of the [`Heartbeat`]s messages if they are requested with the query parameter. +//! //! The route is handled by [`channel::last_approved()`]. //! //! Request query parameters: [`LastApprovedQuery`][primitives::sentry::LastApprovedQuery] @@ -184,10 +224,18 @@ //! //! The route is handled by [`channel::channel_payout()`]. //! -//! Request JSON body: [`ChannelPayRequest`](primitives::sentry::ChannelPayRequest) +//! Request JSON body: [`ChannelPayRequest`] //! //! Response: [`SuccessResponse`](primitives::sentry::SuccessResponse) //! +//! ##### Examples +//! +//! Request (json): +//! +//! ``` +#![doc = include_str!("../../primitives/examples/channel_pay_request.rs")] +//! ``` +//! //! //! #### GET `/v5/channel/:id/get-leaf` //! @@ -289,7 +337,7 @@ //! ##### Examples //! //! ``` -#![doc = include_str!("../../primitives/examples/create_campaign.rs")] +#![doc = include_str!("../../primitives/examples/create_campaign_request.rs")] //! ``` //! //! #### POST `/v5/campaign/:id` (auth required) @@ -307,7 +355,7 @@ //! ##### Examples //! //! ``` -#![doc = include_str!("../../primitives/examples/modify_campaign.rs")] +#![doc = include_str!("../../primitives/examples/modify_campaign_request.rs")] //! ``` //! //! #### POST `/v5/campaign/:id/events` @@ -416,6 +464,7 @@ //! [`ApproveState`]: primitives::validator::ApproveState //! [`Accounting`]: crate::db::accounting::Accounting //! [`AccountingResponse`]: primitives::sentry::AccountingResponse +//! [`AllSpendersResponse`]: primitives::sentry::AllSpendersResponse //! [`AnalyticsResponse`]: primitives::sentry::AnalyticsResponse //! [`AnalyticsQuery`]: primitives::analytics::AnalyticsQuery //! [`Auth.uid`]: crate::Auth::uid @@ -427,12 +476,14 @@ //! [`Channel.leader`]: primitives::Channel::leader //! [`Channel.follower`]: primitives::Channel::follower //! [`ChannelId`]: primitives::ChannelId +//! [`ChannelPayRequest`]: primitives::sentry::ChannelPayRequest //! [`check_access()`]: crate::access::check_access //! [`Config.msgs_find_limit`]: primitives::Config::msgs_find_limit //! [`Event`]: primitives::sentry::Event //! [`Heartbeat`]: primitives::validator::Heartbeat //! [`MessageTypes`]: primitives::validator::MessageTypes //! [`NewState`]: primitives::validator::NewState +//! [`SpenderResponse`]: primitives::sentry::SpenderResponse //! [`SuccessResponse`]: primitives::sentry::SuccessResponse //! [`ValidatorId`]: primitives::ValidatorId diff --git a/sentry/src/routes/channel.rs b/sentry/src/routes/channel.rs index a0e4e6aab..c8d6e04f1 100644 --- a/sentry/src/routes/channel.rs +++ b/sentry/src/routes/channel.rs @@ -610,7 +610,7 @@ pub async fn channel_dummy_deposit( /// pub mod validator_message { use crate::{ - db::validator_message::{get_validator_messages, insert_validator_messages}, + db::validator_message::{get_validator_messages, insert_validator_message}, Auth, }; use crate::{ @@ -740,7 +740,7 @@ pub mod validator_message { None => Err(ResponseError::Unauthorized), _ => { try_join_all(create_request.messages.iter().map(|message| { - insert_validator_messages(&app.pool, &channel, &auth.uid, message) + insert_validator_message(&app.pool, &channel, &auth.uid, message) })) .await?;