From 6976b51a86e66c26523869eccbb19196e949c7b1 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Wed, 6 Jul 2022 17:40:56 +0300 Subject: [PATCH 01/23] initial documentation --- Cargo.lock | 46 +----------- primitives/Cargo.toml | 6 +- primitives/examples/analytics_query.rs | 0 primitives/examples/analytics_response.rs | 0 primitives/examples/create_campaign.rs | 86 ++++++++++++++++++++++ primitives/examples/modify_campaign.rs | 0 primitives/src/campaign_validator.rs | 2 +- primitives/src/sentry.rs | 13 +++- sentry/docs/analytics/examples.js | 0 sentry/docs/analytics/glossary.md | 9 +++ sentry/docs/analytics/tutorial.md | 14 ++++ sentry/docs/create_campaign/examples.js | 61 ++++++++++++++++ sentry/docs/create_campaign/examples.rs | 88 +++++++++++++++++++++++ sentry/docs/create_campaign/glossary.md | 15 ++++ sentry/docs/create_campaign/tutorial.md | 12 ++++ sentry/docs/modify_campaign/examples.js | 0 sentry/docs/modify_campaign/glossary.md | 0 sentry/docs/modify_campaign/tutorial.md | 0 sentry/docs/post_events/examples.js | 0 sentry/docs/post_events/glossary.md | 15 ++++ sentry/docs/post_events/tutorial.md | 9 +++ sentry/src/routes/analytics.rs | 11 +++ sentry/src/routes/campaign.rs | 40 ++++++++++- sentry/src/routes/routers.rs | 1 + 24 files changed, 378 insertions(+), 50 deletions(-) create mode 100644 primitives/examples/analytics_query.rs create mode 100644 primitives/examples/analytics_response.rs create mode 100644 primitives/examples/create_campaign.rs create mode 100644 primitives/examples/modify_campaign.rs create mode 100644 sentry/docs/analytics/examples.js create mode 100644 sentry/docs/analytics/glossary.md create mode 100644 sentry/docs/analytics/tutorial.md create mode 100644 sentry/docs/create_campaign/examples.js create mode 100644 sentry/docs/create_campaign/examples.rs create mode 100644 sentry/docs/create_campaign/glossary.md create mode 100644 sentry/docs/create_campaign/tutorial.md create mode 100644 sentry/docs/modify_campaign/examples.js create mode 100644 sentry/docs/modify_campaign/glossary.md create mode 100644 sentry/docs/modify_campaign/tutorial.md create mode 100644 sentry/docs/post_events/examples.js create mode 100644 sentry/docs/post_events/glossary.md create mode 100644 sentry/docs/post_events/tutorial.md diff --git a/Cargo.lock b/Cargo.lock index d4d042c13..1a078bbe1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,15 +140,6 @@ version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" -[[package]] -name = "array-init" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" -dependencies = [ - "nodrop", -] - [[package]] name = "array-init" version = "2.0.0" @@ -1838,12 +1829,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "md-5" version = "0.10.1" @@ -2029,12 +2014,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "num" version = "0.4.0" @@ -2328,7 +2307,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec 1.8.0", + "smallvec", "windows-sys", ] @@ -2595,7 +2574,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebd6e8b7189a73169290e89bd24c771071f1012d8fe6f738f5226531f0b03d89" dependencies = [ - "array-init 2.0.0", + "array-init", "bytes", "chrono", "fallible-iterator", @@ -2669,7 +2648,6 @@ dependencies = [ "parse-display", "pretty_assertions", "serde", - "serde-hex", "serde_json", "serde_millis", "serde_qs 0.9.2", @@ -3201,17 +3179,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-hex" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca37e3e4d1b39afd7ff11ee4e947efae85adfddf4841787bfa47c470e96dc26d" -dependencies = [ - "array-init 0.0.4", - "serde", - "smallvec 0.6.14", -] - [[package]] name = "serde_derive" version = "1.0.137" @@ -3457,15 +3424,6 @@ dependencies = [ "deunicode", ] -[[package]] -name = "smallvec" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] - [[package]] name = "smallvec" version = "1.8.0" diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 30eb283ca..2e8e0dc16 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -32,12 +32,14 @@ required-features = ["test-util"] name = "campaign_list_response" required-features = ["test-util"] +[[example]] +name = "create_campaign" +required-features = ["test-util"] + [dependencies] # (De)Serialization serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -# TODO: Remove once we change `ChannelId` Serialize impl -serde-hex = "0.1" serde_millis = "0.1" # Used prefixes on field for targeting::Input, and `campaign::Active` serde_with = "1" diff --git a/primitives/examples/analytics_query.rs b/primitives/examples/analytics_query.rs new file mode 100644 index 000000000..e69de29bb diff --git a/primitives/examples/analytics_response.rs b/primitives/examples/analytics_response.rs new file mode 100644 index 000000000..e69de29bb diff --git a/primitives/examples/create_campaign.rs b/primitives/examples/create_campaign.rs new file mode 100644 index 000000000..347271847 --- /dev/null +++ b/primitives/examples/create_campaign.rs @@ -0,0 +1,86 @@ +use primitives::{sentry::campaign_create::CreateCampaign, test_util::DUMMY_CAMPAIGN, CampaignId}; +use std::str::FromStr; +use serde_json::json; + +fn main() { + // CreateCampaign in an HTTP request + { + let create_campaign = CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None); + + let create_campaign_as_json_str = json!("{ + \"id\":null, + \"channel\":{ + \"leader\":\"0x80690751969B234697e9059e04ed72195c3507fa\", + \"follower\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", + \"guardian\":\"0xe061E1EB461EaBE512759aa18A201B20Fe90631D\", + \"token\":\"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E\", + \"nonce\":\"987654321\" + }, + \"creator\":\"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F\", + \"budget\":\"100000000000\", + \"validators\":[ + { + \"id\":\"0x80690751969B234697e9059e04ed72195c3507fa\", + \"fee\":\"2000000\", + \"url\":\"http://localhost:8005\" + }, + { + \"id\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", + \"fee\":\"3000000\", + \"url\":\"http://localhost:8006\" + } + ], + \"title\":\"Dummy Campaign\", + \"pricingBounds\":{\"CLICK\":{\"min\":\"0\",\"max\":\"0\"},\"IMPRESSION\":{\"min\":\"1\",\"max\":\"10\"}}, + \"eventSubmission\":{\"allow\":[]}, + \"targetingRules\":[], + \"created\":1612162800000, + \"active_to\":4073414400000 + }"); + + let create_campaign_from_json = serde_json::from_str(create_campaign_as_json_str).expect("should deserialize"); + + assert_eq!(create_campaign, create_campaign_from_json); + } + + // CreateCampaign with a provided ID + { + let mut create_campaign = CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None); + create_campaign.id = Some(CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8").expect("Should be valid id")); + + let create_campaign_as_json_str = "{ + \"id\":\"0x936da01f9abd4d9d80c702af85c822a8\", + \"channel\":{ + \"leader\":\"0x80690751969B234697e9059e04ed72195c3507fa\", + \"follower\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", + \"guardian\":\"0xe061E1EB461EaBE512759aa18A201B20Fe90631D\", + \"token\":\"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E\", + \"nonce\":\"987654321\" + }, + \"creator\":\"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F\", + \"budget\":\"100000000000\", + \"validators\":[ + { + \"id\":\"0x80690751969B234697e9059e04ed72195c3507fa\", + \"fee\":\"2000000\", + \"url\":\"http://localhost:8005\" + }, + { + \"id\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", + \"fee\":\"3000000\", + \"url\":\"http://localhost:8006\" + } + ], + \"title\":\"Dummy Campaign\", + \"pricingBounds\":{\"CLICK\":{\"min\":\"0\",\"max\":\"0\"},\"IMPRESSION\":{\"min\":\"1\",\"max\":\"10\"}}, + \"eventSubmission\":{\"allow\":[]}, + \"targetingRules\":[], + \"created\":1612162800000, + \"active_to\":4073414400000 + }"; + + let create_campaign_from_json = serde_json::from_str(create_campaign_as_json_str).expect("should deserialize"); + + assert_eq!(create_campaign, create_campaign_from_json); + } +} \ No newline at end of file diff --git a/primitives/examples/modify_campaign.rs b/primitives/examples/modify_campaign.rs new file mode 100644 index 000000000..e69de29bb diff --git a/primitives/src/campaign_validator.rs b/primitives/src/campaign_validator.rs index 0a0a640d9..05e244ef7 100644 --- a/primitives/src/campaign_validator.rs +++ b/primitives/src/campaign_validator.rs @@ -72,7 +72,7 @@ impl Validator for Campaign { .find_chain_of(self.channel.token) .ok_or(Validation::UnlistedAsset)?; - // Check if the campaign budget is above the minimum deposit configured + // Check if the campaign budget is above the minimum campaign budget configured if self .budget .to_precision(chain_context.token.precision.get()) diff --git a/primitives/src/sentry.rs b/primitives/src/sentry.rs index 90694cf1b..53f1267ec 100644 --- a/primitives/src/sentry.rs +++ b/primitives/src/sentry.rs @@ -802,6 +802,12 @@ pub mod campaign_create { /// All fields are present except the `CampaignId` which is randomly created /// This struct defines the Body of the request (in JSON) + /// + /// # Examples + /// + /// ``` + #[doc = include_str!("../examples/create_campaign.rs")] + /// ``` #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct CreateCampaign { @@ -889,7 +895,12 @@ pub mod campaign_modify { AdUnit, Campaign, EventSubmission, UnifiedNum, }; - // All editable fields stored in one place, used for checking when a budget is changed + /// All editable fields stored in one place, used for checking when a budget is changed + /// + /// # Examples: + /// ``` + #[doc = include_str!("../examples/modify_campaign.rs")] + /// ``` #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct ModifyCampaign { pub budget: Option, diff --git a/sentry/docs/analytics/examples.js b/sentry/docs/analytics/examples.js new file mode 100644 index 000000000..e69de29bb diff --git a/sentry/docs/analytics/glossary.md b/sentry/docs/analytics/glossary.md new file mode 100644 index 000000000..c37a04876 --- /dev/null +++ b/sentry/docs/analytics/glossary.md @@ -0,0 +1,9 @@ +[`AllowedKey`](`primitives::query::AllowedKey`) + +[`Analytics`](`primitives::Analytics`) + +[`Auth`](crate::Auth) + +[`ResponseError`](`crate::response::ResponseError`) + +[`AnalyticsQuery`](`primitives::AnalyticsQuery`) \ No newline at end of file diff --git a/sentry/docs/analytics/tutorial.md b/sentry/docs/analytics/tutorial.md new file mode 100644 index 000000000..bf9f33c1d --- /dev/null +++ b/sentry/docs/analytics/tutorial.md @@ -0,0 +1,14 @@ +In order to call the route successfully you need to: +- GET `/analytics`: + - Use a valid `AnalyticsQuery`, the only keys that you can use are `AllowedKey::Country` and `AllowedKey::AdSlotType` + - If you are using `?segmentBy=...` query parameter it must also be an allowed key, otherwise an error will be returned +- GET `/analytics/for-publisher` (auth required): + - Returns all analytics where the currently authenticated address `Auth.uid` is a **publisher**. + - Use a valid `AnalyticsQuery` with no restrictons on the allowed keys that you can use. +- GET `/analytics/for-advertiser`: + - Returns all analytics where the currently authenticated address `Auth.uid` is a **advertiser**. + - Use a valid `AnalyticsQuery` with no restrictons on the allowed keys that you can use. +- GET `/analytics/for/admin`: + - Admin access to the analytics with no restrictions on the keys for filtering. + - Use a valid `AnalyticsQuery` with no restrictons on the allowed keys that you can use. +- If the request is successful your output will be an array with all the fetched entries which include the time, the value of the metric from the query and the `segment_by` field diff --git a/sentry/docs/create_campaign/examples.js b/sentry/docs/create_campaign/examples.js new file mode 100644 index 000000000..30bf3c325 --- /dev/null +++ b/sentry/docs/create_campaign/examples.js @@ -0,0 +1,61 @@ +const request = require('request'); + +const valid_campaign = { + id: null, + channel: { + leader: "0x80690751969B234697e9059e04ed72195c3507fa", + follower: "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", + guardian: "0xe061E1EB461EaBE512759aa18A201B20Fe90631D", + token: "0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E", + nonce: 987654321, + }, + creator:"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F", + budget:100000000000, + validators: [ + { + id:"0x80690751969B234697e9059e04ed72195c3507fa", + fee:2000000, + url:"http://localhost:8005" + }, + { + id:"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", + fee:3000000, + url:"http://localhost:8006" + } + ], + title:"Dummy Campaign", + pricingBounds:{ + CLICK: {min: 0,max:0 }, + IMPRESSION:{min:1,max:10}}, + eventSubmission:{allow:[]}, + targetingRules:[], + created:1612162800000, + active_to:4073414400000 +} +const chain = { + chain_id: 1, + rpc: "http://dummy.com", + outpace: "0x0000000000000000000000000000000000000000" +}; + +// Valid Request +const auth = { + era: 0, + uid: "0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F", + chain, +}; + + +request({ + url: "http://localhost:8005/v5/campaign", + method: "POST", + json: true, // <--Very important!!! + body: JSON.stringify(valid_campaign) +}, (err, res, body) => { + console.log(res); // should be a Campaign object +}); + +// Request with a broken campaign + + +// Request from a different creator \ No newline at end of file diff --git a/sentry/docs/create_campaign/examples.rs b/sentry/docs/create_campaign/examples.rs new file mode 100644 index 000000000..5ae7f3b1e --- /dev/null +++ b/sentry/docs/create_campaign/examples.rs @@ -0,0 +1,88 @@ +fn main() { + let valid_campaign = json!("{ + \"id\":null, + \"channel\":{ + \"leader\":\"0x80690751969B234697e9059e04ed72195c3507fa\", + \"follower\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", + \"guardian\":\"0xe061E1EB461EaBE512759aa18A201B20Fe90631D\", + \"token\":\"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E\", + \"nonce\":\"987654321\" + }, + \"creator\":\"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F\", + \"budget\":\"100000000000\", + \"validators\":[ + { + \"id\":\"0x80690751969B234697e9059e04ed72195c3507fa\", + \"fee\":\"2000000\", + \"url\":\"http://localhost:8005\" + }, + { + \"id\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", + \"fee\":\"3000000\", + \"url\":\"http://localhost:8006\" + } + ], + \"title\":\"Dummy Campaign\", + \"pricingBounds\":{\"CLICK\":{\"min\":\"0\",\"max\":\"0\"},\"IMPRESSION\":{\"min\":\"1\",\"max\":\"10\"}}, + \"eventSubmission\":{\"allow\":[]}, + \"targetingRules\":[], + \"created\":1612162800000, + \"active_to\":4073414400000 + }"); + + let not_a_valid_campaign = json!("{ + \"id\":null, + \"channel\": 1234567890, + }"); + + // Valid request + { + let auth = Auth { + era: 0, + uid: ValidatorId::from("0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F"), + chain: channel_context.chain.clone(), + }; + + let req = Request::builder() + .extension(auth) + .body(valid_campaign) + .expect("Should build Request"); + + // Should return a Campaign + let res = make_request("POST", "/v5/campaign/", req); + } + + // Request with an invalid campaign + { + let auth = Auth { + era: 0, + uid: ValidatorId::from("0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F"), + chain: channel_context.chain.clone(), + }; + + let req = Request::builder() + .extension(auth) + .body(not_a_valid_campaign) + .expect("Should build Request"); + + // ResponseError::FailedValidation + let res = make_request("POST", "/v5/campaign/", req); + } + + // Request not sent by creator + { + let auth = Auth { + era: 0, + uid: ValidatorId::from("0x0000000000000000000000000000000000000000"), // not the creator + chain: channel_context.chain.clone(), + }; + + let req = Request::builder() + .extension(auth) + .body(valid_campaign) + .expect("Should build Request"); + + // ResponseError::Forbidden + let res = make_request("POST", "/v5/campaign/", req); + } +} \ No newline at end of file diff --git a/sentry/docs/create_campaign/glossary.md b/sentry/docs/create_campaign/glossary.md new file mode 100644 index 000000000..e5ab12463 --- /dev/null +++ b/sentry/docs/create_campaign/glossary.md @@ -0,0 +1,15 @@ +[`Campaign`](`primitives::campaign::Campaign`) + +[`CreateCampaign`](`primitives::sentry::campaign_create::CreateCampaign`) + +[`Channel`](`primitives::Channel`) + +[`Spendable`](`primitives::spender::Spendable`) + +[`CampaignId`](`primitives::CampaignId`) + +[`Validator`](`primitives::campaign_validator::Validator`) + +[`Auth`](`crate::application::Auth`) + +[`ResponseError`](`crate::response::ResponseError`) \ No newline at end of file diff --git a/sentry/docs/create_campaign/tutorial.md b/sentry/docs/create_campaign/tutorial.md new file mode 100644 index 000000000..8d1543c5e --- /dev/null +++ b/sentry/docs/create_campaign/tutorial.md @@ -0,0 +1,12 @@ +In order to call the route successfully you need to: +- Have a valid `CreateCampaign` struct as the request body, which is the same as the `Campaign` struct except that the `id` field is optional. (`CampaignId` is generated on creation) +(TODO: Explain when and why an id will be provided) +- Request must be sent by the `Campaign.creator`. (`Auth.uid == Campaign.creator`) +- The campaign must pass the validations made by `Validator.validate()` which include: + - Ensuring the `Channel`'s validators include the adapter identity + - Ensuring the `active.to` field is a valid future date + - Ensuring the `Campaign.validators`, `Campaign.creator` and `Channel.token` are whitelisted + - Ensuring the campaign budget is above the minimum campaign budget configured + - Ensuring the validator fee is greater than the minimum configured fee +- If any of the requirements are not met a `ResponseError` will be returned +- On a successful request your response will be a serialied `Campaign` struct \ No newline at end of file diff --git a/sentry/docs/modify_campaign/examples.js b/sentry/docs/modify_campaign/examples.js new file mode 100644 index 000000000..e69de29bb diff --git a/sentry/docs/modify_campaign/glossary.md b/sentry/docs/modify_campaign/glossary.md new file mode 100644 index 000000000..e69de29bb diff --git a/sentry/docs/modify_campaign/tutorial.md b/sentry/docs/modify_campaign/tutorial.md new file mode 100644 index 000000000..e69de29bb diff --git a/sentry/docs/post_events/examples.js b/sentry/docs/post_events/examples.js new file mode 100644 index 000000000..e69de29bb diff --git a/sentry/docs/post_events/glossary.md b/sentry/docs/post_events/glossary.md new file mode 100644 index 000000000..ebcc950d8 --- /dev/null +++ b/sentry/docs/post_events/glossary.md @@ -0,0 +1,15 @@ +[`Event`](`primitives::Event`) + +[`EventType`](`primitives::EventType`) + +[`Campaign`](`primitives::Campaign`) + +[`check_access()`](`sentry::access::check_access`) + +[`Rule`](`primitives::event_submission::Rule`) + +[`apply_rule()`](`sentry::access::apply_rule`) + +[`EventError`](`sentry::routes::campaign::insert_events::EventError`) + +[`record()`](`sentry::analytics::record`) \ No newline at end of file diff --git a/sentry/docs/post_events/tutorial.md b/sentry/docs/post_events/tutorial.md new file mode 100644 index 000000000..4b86cf60a --- /dev/null +++ b/sentry/docs/post_events/tutorial.md @@ -0,0 +1,9 @@ +In order to call the route successfully you need to: +- You must send an authenticated request with a body which is an array of events in the + form of `{ events: [ ... ] }` +- The events must pass all the rules in `check_access(...)` that include: + - The campaign shouldn't be expired + - The session shouldn't be from a forbidden country or referrer + - The events should apply all applicable access rules - either the ones provided in `Campaign.event_submission` or the default ones (uid = campaign.creator). The rule uid's should include the `Auth.uid` to make a rule applicable. If a Rule is found which has a `rate_limit` field, then rate limit is applied using `apply_rule(...)` +- Publisher payouts and validator fees are calculated, this step can fail if the campaign has no remaining bduget. If remaining is negative after Redis/Postgres are updated an `EventError` will be thrown +- Successfully paid events will be recorded in analytics (see [`record(...)`](`sentry::analytics::record`)) diff --git a/sentry/src/routes/analytics.rs b/sentry/src/routes/analytics.rs index 3f70eee18..94d2bccdc 100644 --- a/sentry/src/routes/analytics.rs +++ b/sentry/src/routes/analytics.rs @@ -17,6 +17,17 @@ use primitives::analytics::{ /// `GET /v5/analytics` request /// with query parameters: [`primitives::analytics::AnalyticsQuery`]. +/// +/// # Tutorial: +#[doc = include_str!("../../docs/create_campaign/tutorial.md")] +/// +/// # Examples: +/// ``` +#[doc = include_str!("../../docs/create_campaign/examples.rs")] +/// ``` +/// +/// # Glossary: +#[doc = include_str!("../../docs/create_campaign/glossary.md")] pub async fn analytics( req: Request, app: &Application, diff --git a/sentry/src/routes/campaign.rs b/sentry/src/routes/campaign.rs index 503b790a1..3961fc5fa 100644 --- a/sentry/src/routes/campaign.rs +++ b/sentry/src/routes/campaign.rs @@ -143,6 +143,19 @@ pub async fn fetch_campaign_ids_for_channel( } /// POST `/v5/campaign` +/// +/// Creates a new `Campaign` making sure its `Channel` exists if it's new and updates the `Spendable` entry. +/// +/// # Tutorial: +#[doc = include_str!("../../docs/create_campaign/tutorial.md")] +/// +/// # Examples: +/// ``` +#[doc = include_str!("../../docs/create_campaign/examples.rs")] +/// ``` +/// +/// # Glossary: +#[doc = include_str!("../../docs/create_campaign/glossary.md")] pub async fn create_campaign( req: Request, app: &Application, @@ -317,7 +330,7 @@ pub async fn campaign_list( /// POST `/v5/campaign/:id/close` (auth required) /// -/// Can only be called by the [`Campaign.creator`]! +/// **Can only be called by the [`Campaign.creator`]!** /// To close a campaign, just set it's budget to what it's spent so far (so that remaining == 0) /// newBudget = totalSpent, i.e. newBudget = oldBudget - remaining pub async fn close_campaign( @@ -369,6 +382,14 @@ pub mod update_campaign { use super::*; /// POST `/v5/campaign/:id` (auth required) + /// + /// Request body is [`ModifyCampaign`](`primitives::sentry::campaign_modify::ModifyCampaign`) + /// which consists of all the editable fields of the [`Campaign`] + /// + /// Returns the updated [`Campaign`] serialized + /// + /// Ensures that the remaining funds for all campaigns <= total remaining funds (total deposited - total spent) + /// pub async fn handle_route( req: Request, app: &Application, @@ -628,7 +649,22 @@ pub mod insert_events { CampaignOutOfBudget, } - /// POST `/v5/campaign/:id` + /// POST `/v5/campaign/:id/events` + /// + /// The route is used to send events (`IMPRESSION`/`CLICK`) for the provided `Campaign`. + /// First we verify that all the rules are met for each event, we then caluclate the payputs and validator fees + /// and we record the changes in Postgres/Redis. After that we record the events in the analytics. + /// + /// # Tutorial: + #[doc = include_str!("../../docs/post_events/tutorial.md")] + /// + /// # Examples: + /// ``` + #[doc = include_str!("../../docs/post_events/examples.rs")] + /// ``` + /// + /// # Glossary: + #[doc = include_str!("../../docs/post_events/glossary.md")] pub async fn handle_route( req: Request, app: &Application, diff --git a/sentry/src/routes/routers.rs b/sentry/src/routes/routers.rs index ad1d51b35..d388c3f91 100644 --- a/sentry/src/routes/routers.rs +++ b/sentry/src/routes/routers.rs @@ -247,6 +247,7 @@ pub async fn channels_router( } } +/// `/v5/campaign` router pub async fn campaigns_router( mut req: Request, app: &Application, From f2ed51e09896821bdeec73b286cd90af32638004 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Wed, 6 Jul 2022 19:00:33 +0300 Subject: [PATCH 02/23] changed example comments --- sentry/docs/create_campaign/examples.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry/docs/create_campaign/examples.js b/sentry/docs/create_campaign/examples.js index 30bf3c325..96f964548 100644 --- a/sentry/docs/create_campaign/examples.js +++ b/sentry/docs/create_campaign/examples.js @@ -49,10 +49,10 @@ const auth = { request({ url: "http://localhost:8005/v5/campaign", method: "POST", - json: true, // <--Very important!!! + json: true, body: JSON.stringify(valid_campaign) }, (err, res, body) => { - console.log(res); // should be a Campaign object + console.log(body); // should be a Campaign object }); // Request with a broken campaign From ea5b20f561d7c5369ed41a5338cc0bd38e12f660 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Wed, 6 Jul 2022 19:26:07 +0300 Subject: [PATCH 03/23] changed example comments --- sentry/docs/create_campaign/examples.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/docs/create_campaign/examples.js b/sentry/docs/create_campaign/examples.js index 96f964548..f79e52182 100644 --- a/sentry/docs/create_campaign/examples.js +++ b/sentry/docs/create_campaign/examples.js @@ -49,7 +49,7 @@ const auth = { request({ url: "http://localhost:8005/v5/campaign", method: "POST", - json: true, + json: true, body: JSON.stringify(valid_campaign) }, (err, res, body) => { console.log(body); // should be a Campaign object From bad29e174fefffcc7677d139edc5903d0edc5917 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Tue, 2 Aug 2022 16:04:10 +0300 Subject: [PATCH 04/23] more code examples + fixes --- Cargo.lock | 46 ++++++++++- primitives/Cargo.toml | 2 + primitives/examples/analytics_query.rs | 80 +++++++++++++++++++ primitives/examples/create_campaign.rs | 106 +++++++++++++------------ primitives/examples/modify_campaign.rs | 32 ++++++++ sentry/src/routes/analytics.rs | 17 ++-- sentry/src/routes/campaign.rs | 28 +------ 7 files changed, 223 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a078bbe1..d4d042c13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,6 +140,15 @@ version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" +[[package]] +name = "array-init" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23589ecb866b460d3a0f1278834750268c607e8e28a1b982c907219f3178cd72" +dependencies = [ + "nodrop", +] + [[package]] name = "array-init" version = "2.0.0" @@ -1829,6 +1838,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "md-5" version = "0.10.1" @@ -2014,6 +2029,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "num" version = "0.4.0" @@ -2307,7 +2328,7 @@ dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec", + "smallvec 1.8.0", "windows-sys", ] @@ -2574,7 +2595,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebd6e8b7189a73169290e89bd24c771071f1012d8fe6f738f5226531f0b03d89" dependencies = [ - "array-init", + "array-init 2.0.0", "bytes", "chrono", "fallible-iterator", @@ -2648,6 +2669,7 @@ dependencies = [ "parse-display", "pretty_assertions", "serde", + "serde-hex", "serde_json", "serde_millis", "serde_qs 0.9.2", @@ -3179,6 +3201,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-hex" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca37e3e4d1b39afd7ff11ee4e947efae85adfddf4841787bfa47c470e96dc26d" +dependencies = [ + "array-init 0.0.4", + "serde", + "smallvec 0.6.14", +] + [[package]] name = "serde_derive" version = "1.0.137" @@ -3424,6 +3457,15 @@ dependencies = [ "deunicode", ] +[[package]] +name = "smallvec" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" +dependencies = [ + "maybe-uninit", +] + [[package]] name = "smallvec" version = "1.8.0" diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 2e8e0dc16..36a0f5321 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -40,6 +40,8 @@ required-features = ["test-util"] # (De)Serialization serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +# TODO: Remove once we change `ChannelId` Serialize impl +serde-hex = "0.1" serde_millis = "0.1" # Used prefixes on field for targeting::Input, and `campaign::Active` serde_with = "1" diff --git a/primitives/examples/analytics_query.rs b/primitives/examples/analytics_query.rs index e69de29bb..7395a920f 100644 --- a/primitives/examples/analytics_query.rs +++ b/primitives/examples/analytics_query.rs @@ -0,0 +1,80 @@ +use primitives::{ + analytics::{AnalyticsQuery, Metric, Timeframe, query::{AllowedKey, Time}, OperatingSystem}, + sentry::{EventType, DateHour}, + Address, CampaignId, ChainId, IPFS +}; +use std::str::FromStr; + +fn main() { + // Empty query - default values only + { + let empty_query = ""; + let query: AnalyticsQuery = serde_qs::from_str(empty_query).unwrap(); + + assert_eq!(100, query.limit); + assert_eq!(EventType::Impression, query.event_type); + assert!(matches!(query.metric, Metric::Count)); + assert!(matches!(query.time.timeframe, Timeframe::Day)); + } + // Query with different metric/chain/eventType + { + let query_str = "limit=200&eventType=CLICK&metric=paid&timeframe=month"; + let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap(); + + assert_eq!(200, query.limit); + assert_eq!(EventType::Click, query.event_type); + assert!(matches!(query.metric, Metric::Paid)); + assert!(matches!(query.time.timeframe, Timeframe::Month)); + } + + // Query with allowed keys for guest - country, slotType + { + let query_str = "country=Bulgaria&adSlotType=legacy_300x100"; + let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap(); + + assert_eq!(Some("Bulgaria".to_string()), query.country); + assert_eq!(Some("legacy_300x100".to_string()), query.ad_slot_type); + } + + // Query with all possible fields (publisher/advertiser/admin) + { + let query_str = r#"limit=200 + &eventType=CLICK + &metric=paid + &segmentBy=country + &timeframe=week + &start=420 + &campaignId=0x936da01f9abd4d9d80c702af85c822a8 + &adUnit=Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f + &adSlot=QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR + &adSlotType=legacy_300x100 + &avertiser=0xDd589B43793934EF6Ad266067A0d1D4896b0dff0 + &publisher=0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9 + &hostname=localhost + &country=Bulgaria + &osName=Windows + &chains[0]=1&chains[1]=1337 + "#; + let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap(); + + assert_eq!(query.limit, 200); + assert_eq!(query.event_type, EventType::Click); + assert!(matches!(query.metric, Metric::Paid)); + assert_eq!(query.segment_by, Some(AllowedKey::Country)); + assert_eq!(query.time, Time { + timeframe: Timeframe::Week, + start: DateHour::from_ymdh(2021, 12, 31, 22), + end: None, + }); + assert_eq!(query.campaign_id, Some(CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8").expect("should be valid"))); + assert_eq!(query.ad_unit, Some(IPFS::from_str("Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f").expect("should be valid"))); + assert_eq!(query.ad_slot, Some(IPFS::from_str("QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR").expect("should be valid"))); + assert_eq!(query.ad_slot_type, Some("legacy_300x100".to_string())); + assert_eq!(query.advertiser, Some(Address::from_str("0xDd589B43793934EF6Ad266067A0d1D4896b0dff0").expect("should be valid"))); + assert_eq!(query.publisher, Some(Address::from_str("0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9").expect("should be valid"))); + assert_eq!(query.hostname, Some("localhost".to_string())); + assert_eq!(query.country, Some("Bulgaria".to_string())); + assert_eq!(query.os_name, Some(OperatingSystem::Whitelisted("Windows".to_string()))); + assert_eq!(query.chains, vec!(ChainId::new(1), ChainId::new(1337))); + } +} \ No newline at end of file diff --git a/primitives/examples/create_campaign.rs b/primitives/examples/create_campaign.rs index 347271847..0691aa862 100644 --- a/primitives/examples/create_campaign.rs +++ b/primitives/examples/create_campaign.rs @@ -7,40 +7,41 @@ fn main() { { let create_campaign = CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None); - let create_campaign_as_json_str = json!("{ - \"id\":null, - \"channel\":{ - \"leader\":\"0x80690751969B234697e9059e04ed72195c3507fa\", - \"follower\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", - \"guardian\":\"0xe061E1EB461EaBE512759aa18A201B20Fe90631D\", - \"token\":\"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E\", - \"nonce\":\"987654321\" + let create_campaign_json = json!({ + "id":null, + "channel":{ + "leader":"0x80690751969B234697e9059e04ed72195c3507fa", + "follower":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", + "guardian":"0xe061E1EB461EaBE512759aa18A201B20Fe90631D", + "token":"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E", + "nonce":"987654321" }, - \"creator\":\"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F\", - \"budget\":\"100000000000\", - \"validators\":[ + "creator":"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F", + "budget":"100000000000", + "validators":[ { - \"id\":\"0x80690751969B234697e9059e04ed72195c3507fa\", - \"fee\":\"2000000\", - \"url\":\"http://localhost:8005\" + "id":"0x80690751969B234697e9059e04ed72195c3507fa", + "fee":"2000000", + "url":"http://localhost:8005" }, { - \"id\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", - \"fee\":\"3000000\", - \"url\":\"http://localhost:8006\" + "id":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", + "fee":"3000000", + "url":"http://localhost:8006" } ], - \"title\":\"Dummy Campaign\", - \"pricingBounds\":{\"CLICK\":{\"min\":\"0\",\"max\":\"0\"},\"IMPRESSION\":{\"min\":\"1\",\"max\":\"10\"}}, - \"eventSubmission\":{\"allow\":[]}, - \"targetingRules\":[], - \"created\":1612162800000, - \"active_to\":4073414400000 - }"); + "title":"Dummy Campaign", + "pricingBounds":{"CLICK":{"min":"0","max":"0"},"IMPRESSION":{"min":"1","max":"10"}}, + "eventSubmission":{"allow":[]}, + "targetingRules":[], + "created":1612162800000_u64, + "activeTo":4073414400000_u64 + }); - let create_campaign_from_json = serde_json::from_str(create_campaign_as_json_str).expect("should deserialize"); + let create_campaign_json = serde_json::to_string(&create_campaign_json).expect("should serialize"); + let deserialized: CreateCampaign = serde_json::from_str(&create_campaign_json).expect("should deserialize"); - assert_eq!(create_campaign, create_campaign_from_json); + assert_eq!(create_campaign, deserialized); } // CreateCampaign with a provided ID @@ -48,39 +49,40 @@ fn main() { let mut create_campaign = CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None); create_campaign.id = Some(CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8").expect("Should be valid id")); - let create_campaign_as_json_str = "{ - \"id\":\"0x936da01f9abd4d9d80c702af85c822a8\", - \"channel\":{ - \"leader\":\"0x80690751969B234697e9059e04ed72195c3507fa\", - \"follower\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", - \"guardian\":\"0xe061E1EB461EaBE512759aa18A201B20Fe90631D\", - \"token\":\"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E\", - \"nonce\":\"987654321\" + let create_campaign_json = json!({ + "id":"0x936da01f9abd4d9d80c702af85c822a8", + "channel":{ + "leader":"0x80690751969B234697e9059e04ed72195c3507fa", + "follower":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", + "guardian":"0xe061E1EB461EaBE512759aa18A201B20Fe90631D", + "token":"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E", + "nonce":"987654321" }, - \"creator\":\"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F\", - \"budget\":\"100000000000\", - \"validators\":[ + "creator":"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F", + "budget":"100000000000", + "validators":[ { - \"id\":\"0x80690751969B234697e9059e04ed72195c3507fa\", - \"fee\":\"2000000\", - \"url\":\"http://localhost:8005\" + "id":"0x80690751969B234697e9059e04ed72195c3507fa", + "fee":"2000000", + "url":"http://localhost:8005" }, { - \"id\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", - \"fee\":\"3000000\", - \"url\":\"http://localhost:8006\" + "id":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", + "fee":"3000000", + "url":"http://localhost:8006" } ], - \"title\":\"Dummy Campaign\", - \"pricingBounds\":{\"CLICK\":{\"min\":\"0\",\"max\":\"0\"},\"IMPRESSION\":{\"min\":\"1\",\"max\":\"10\"}}, - \"eventSubmission\":{\"allow\":[]}, - \"targetingRules\":[], - \"created\":1612162800000, - \"active_to\":4073414400000 - }"; + "title":"Dummy Campaign", + "pricingBounds":{"CLICK":{"min":"0","max":"0"},"IMPRESSION":{"min":"1","max":"10"}}, + "eventSubmission":{"allow":[]}, + "targetingRules":[], + "created":1612162800000_u64, + "activeTo":4073414400000_u64 + }); - let create_campaign_from_json = serde_json::from_str(create_campaign_as_json_str).expect("should deserialize"); + let create_campaign_json = serde_json::to_string(&create_campaign_json).expect("should serialize"); + let deserialized: CreateCampaign = serde_json::from_str(&create_campaign_json).expect("should deserialize"); - assert_eq!(create_campaign, create_campaign_from_json); + assert_eq!(create_campaign, deserialized); } } \ No newline at end of file diff --git a/primitives/examples/modify_campaign.rs b/primitives/examples/modify_campaign.rs index e69de29bb..a46a56d69 100644 --- a/primitives/examples/modify_campaign.rs +++ b/primitives/examples/modify_campaign.rs @@ -0,0 +1,32 @@ +use primitives::{sentry::campaign_modify::ModifyCampaign, unified_num::FromWhole, UnifiedNum}; +use std::str::FromStr; +use serde_json::json; + +fn main() { + { + let modify_campaign = ModifyCampaign { + ad_units: None, + budget: Some(UnifiedNum::from_whole(100)), + validators: None, + title: None, + pricing_bounds: None, + event_submission: None, + targeting_rules: None, + }; + + let modify_campaign_json = json!({ + "ad_units": null, + "budget": "10000000000", + "validators": null, + "title": null, + "pricing_bounds": null, + "event_submission": null, + "targeting_rules": null, + }); + + let modify_campaign_json = serde_json::to_string(&modify_campaign_json).expect("should serialize"); + let deserialized: ModifyCampaign = serde_json::from_str(&modify_campaign_json).expect("should deserialize"); + + assert_eq!(modify_campaign, deserialized); + } +} \ No newline at end of file diff --git a/sentry/src/routes/analytics.rs b/sentry/src/routes/analytics.rs index 94d2bccdc..2217c2555 100644 --- a/sentry/src/routes/analytics.rs +++ b/sentry/src/routes/analytics.rs @@ -15,19 +15,16 @@ use primitives::analytics::{ AnalyticsQuery, AuthenticateAs, }; -/// `GET /v5/analytics` request +/// GET `/v5/analytics` routes /// with query parameters: [`primitives::analytics::AnalyticsQuery`]. /// -/// # Tutorial: -#[doc = include_str!("../../docs/create_campaign/tutorial.md")] +/// Returns `Vec<[FetchedAnalytics](primitives::sentry::FethedAnalytics)>` +/// Analytics routes: +/// - GET `/v5/analytics` +/// - GET `/v5/analytics/for-publisher` +/// - GET `/v5/analytics/for-advertiser` +/// - GET `/v5/analytics/for-admin` /// -/// # Examples: -/// ``` -#[doc = include_str!("../../docs/create_campaign/examples.rs")] -/// ``` -/// -/// # Glossary: -#[doc = include_str!("../../docs/create_campaign/glossary.md")] pub async fn analytics( req: Request, app: &Application, diff --git a/sentry/src/routes/campaign.rs b/sentry/src/routes/campaign.rs index 3961fc5fa..924b6f14c 100644 --- a/sentry/src/routes/campaign.rs +++ b/sentry/src/routes/campaign.rs @@ -144,18 +144,9 @@ pub async fn fetch_campaign_ids_for_channel( /// POST `/v5/campaign` /// -/// Creates a new `Campaign` making sure its `Channel` exists if it's new and updates the `Spendable` entry. +/// Expected request body: [`CreateCampaign`](`primitives::campaign::campaign_create::CreateCampaign`) /// -/// # Tutorial: -#[doc = include_str!("../../docs/create_campaign/tutorial.md")] -/// -/// # Examples: -/// ``` -#[doc = include_str!("../../docs/create_campaign/examples.rs")] -/// ``` -/// -/// # Glossary: -#[doc = include_str!("../../docs/create_campaign/glossary.md")] +/// Expected response: [Campaign](`primitives::Campaign`) pub async fn create_campaign( req: Request, app: &Application, @@ -651,20 +642,9 @@ pub mod insert_events { /// POST `/v5/campaign/:id/events` /// - /// The route is used to send events (`IMPRESSION`/`CLICK`) for the provided `Campaign`. - /// First we verify that all the rules are met for each event, we then caluclate the payputs and validator fees - /// and we record the changes in Postgres/Redis. After that we record the events in the analytics. - /// - /// # Tutorial: - #[doc = include_str!("../../docs/post_events/tutorial.md")] - /// - /// # Examples: - /// ``` - #[doc = include_str!("../../docs/post_events/examples.rs")] - /// ``` + /// The expected request body is `Vec<[Event](primitives::Event)>` /// - /// # Glossary: - #[doc = include_str!("../../docs/post_events/glossary.md")] + /// The expected Response is [`SuccessResponse`](primitives::sentry::SuccessResponse) pub async fn handle_route( req: Request, app: &Application, From 7a57ce444d017f1f3a628f754c74f3741dc044f4 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Wed, 3 Aug 2022 14:56:19 +0300 Subject: [PATCH 05/23] removed unnecessary file --- primitives/examples/analytics_response.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 primitives/examples/analytics_response.rs diff --git a/primitives/examples/analytics_response.rs b/primitives/examples/analytics_response.rs deleted file mode 100644 index e69de29bb..000000000 From 48bb03c186087a09cbd124d40e22a7eda9322357 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Wed, 3 Aug 2022 14:57:32 +0300 Subject: [PATCH 06/23] formatting --- primitives/examples/analytics_query.rs | 69 ++++++++++++++++++++------ primitives/examples/create_campaign.rs | 23 ++++++--- primitives/examples/modify_campaign.rs | 10 ++-- 3 files changed, 75 insertions(+), 27 deletions(-) diff --git a/primitives/examples/analytics_query.rs b/primitives/examples/analytics_query.rs index 7395a920f..81bc596cf 100644 --- a/primitives/examples/analytics_query.rs +++ b/primitives/examples/analytics_query.rs @@ -1,7 +1,10 @@ use primitives::{ - analytics::{AnalyticsQuery, Metric, Timeframe, query::{AllowedKey, Time}, OperatingSystem}, - sentry::{EventType, DateHour}, - Address, CampaignId, ChainId, IPFS + analytics::{ + query::{AllowedKey, Time}, + AnalyticsQuery, Metric, OperatingSystem, Timeframe, + }, + sentry::{DateHour, EventType}, + Address, CampaignId, ChainId, IPFS, }; use std::str::FromStr; @@ -61,20 +64,56 @@ fn main() { assert_eq!(query.event_type, EventType::Click); assert!(matches!(query.metric, Metric::Paid)); assert_eq!(query.segment_by, Some(AllowedKey::Country)); - assert_eq!(query.time, Time { - timeframe: Timeframe::Week, - start: DateHour::from_ymdh(2021, 12, 31, 22), - end: None, - }); - assert_eq!(query.campaign_id, Some(CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8").expect("should be valid"))); - assert_eq!(query.ad_unit, Some(IPFS::from_str("Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f").expect("should be valid"))); - assert_eq!(query.ad_slot, Some(IPFS::from_str("QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR").expect("should be valid"))); + assert_eq!( + query.time, + Time { + timeframe: Timeframe::Week, + start: DateHour::from_ymdh(2021, 12, 31, 22), + end: None, + } + ); + assert_eq!( + query.campaign_id, + Some( + CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8") + .expect("should be valid") + ) + ); + assert_eq!( + query.ad_unit, + Some( + IPFS::from_str("Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f") + .expect("should be valid") + ) + ); + assert_eq!( + query.ad_slot, + Some( + IPFS::from_str("QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR") + .expect("should be valid") + ) + ); assert_eq!(query.ad_slot_type, Some("legacy_300x100".to_string())); - assert_eq!(query.advertiser, Some(Address::from_str("0xDd589B43793934EF6Ad266067A0d1D4896b0dff0").expect("should be valid"))); - assert_eq!(query.publisher, Some(Address::from_str("0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9").expect("should be valid"))); + assert_eq!( + query.advertiser, + Some( + Address::from_str("0xDd589B43793934EF6Ad266067A0d1D4896b0dff0") + .expect("should be valid") + ) + ); + assert_eq!( + query.publisher, + Some( + Address::from_str("0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9") + .expect("should be valid") + ) + ); assert_eq!(query.hostname, Some("localhost".to_string())); assert_eq!(query.country, Some("Bulgaria".to_string())); - assert_eq!(query.os_name, Some(OperatingSystem::Whitelisted("Windows".to_string()))); + assert_eq!( + query.os_name, + Some(OperatingSystem::Whitelisted("Windows".to_string())) + ); assert_eq!(query.chains, vec!(ChainId::new(1), ChainId::new(1337))); } -} \ No newline at end of file +} diff --git a/primitives/examples/create_campaign.rs b/primitives/examples/create_campaign.rs index 0691aa862..9cfff16ff 100644 --- a/primitives/examples/create_campaign.rs +++ b/primitives/examples/create_campaign.rs @@ -1,6 +1,6 @@ use primitives::{sentry::campaign_create::CreateCampaign, test_util::DUMMY_CAMPAIGN, CampaignId}; -use std::str::FromStr; use serde_json::json; +use std::str::FromStr; fn main() { // CreateCampaign in an HTTP request @@ -38,16 +38,21 @@ fn main() { "activeTo":4073414400000_u64 }); - let create_campaign_json = serde_json::to_string(&create_campaign_json).expect("should serialize"); - let deserialized: CreateCampaign = serde_json::from_str(&create_campaign_json).expect("should deserialize"); + let create_campaign_json = + serde_json::to_string(&create_campaign_json).expect("should serialize"); + let deserialized: CreateCampaign = + serde_json::from_str(&create_campaign_json).expect("should deserialize"); assert_eq!(create_campaign, deserialized); } // CreateCampaign with a provided ID { - let mut create_campaign = CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None); - create_campaign.id = Some(CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8").expect("Should be valid id")); + let mut create_campaign = + CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None); + create_campaign.id = Some( + CampaignId::from_str("0x936da01f9abd4d9d80c702af85c822a8").expect("Should be valid id"), + ); let create_campaign_json = json!({ "id":"0x936da01f9abd4d9d80c702af85c822a8", @@ -80,9 +85,11 @@ fn main() { "activeTo":4073414400000_u64 }); - let create_campaign_json = serde_json::to_string(&create_campaign_json).expect("should serialize"); - let deserialized: CreateCampaign = serde_json::from_str(&create_campaign_json).expect("should deserialize"); + let create_campaign_json = + serde_json::to_string(&create_campaign_json).expect("should serialize"); + let deserialized: CreateCampaign = + serde_json::from_str(&create_campaign_json).expect("should deserialize"); assert_eq!(create_campaign, deserialized); } -} \ No newline at end of file +} diff --git a/primitives/examples/modify_campaign.rs b/primitives/examples/modify_campaign.rs index a46a56d69..3b663f9ca 100644 --- a/primitives/examples/modify_campaign.rs +++ b/primitives/examples/modify_campaign.rs @@ -1,6 +1,6 @@ use primitives::{sentry::campaign_modify::ModifyCampaign, unified_num::FromWhole, UnifiedNum}; -use std::str::FromStr; use serde_json::json; +use std::str::FromStr; fn main() { { @@ -24,9 +24,11 @@ fn main() { "targeting_rules": null, }); - let modify_campaign_json = serde_json::to_string(&modify_campaign_json).expect("should serialize"); - let deserialized: ModifyCampaign = serde_json::from_str(&modify_campaign_json).expect("should deserialize"); + let modify_campaign_json = + serde_json::to_string(&modify_campaign_json).expect("should serialize"); + let deserialized: ModifyCampaign = + serde_json::from_str(&modify_campaign_json).expect("should deserialize"); assert_eq!(modify_campaign, deserialized); } -} \ No newline at end of file +} From d72ec922179e21ed0952794f3e4a2533d336ebc1 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Wed, 3 Aug 2022 15:46:42 +0300 Subject: [PATCH 07/23] fixed CreateCampaign code examples --- primitives/examples/create_campaign.rs | 7 +++++-- primitives/src/analytics.rs | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/primitives/examples/create_campaign.rs b/primitives/examples/create_campaign.rs index 9cfff16ff..f250dd253 100644 --- a/primitives/examples/create_campaign.rs +++ b/primitives/examples/create_campaign.rs @@ -7,6 +7,9 @@ fn main() { { let create_campaign = CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None); + let create_campaign_str = + serde_json::to_string(&create_campaign).expect("should serialize"); + let create_campaign_json = json!({ "id":null, "channel":{ @@ -35,7 +38,7 @@ fn main() { "eventSubmission":{"allow":[]}, "targetingRules":[], "created":1612162800000_u64, - "activeTo":4073414400000_u64 + "active_to":4073414400000_u64 }); let create_campaign_json = @@ -82,7 +85,7 @@ fn main() { "eventSubmission":{"allow":[]}, "targetingRules":[], "created":1612162800000_u64, - "activeTo":4073414400000_u64 + "active_to":4073414400000_u64 }); let create_campaign_json = diff --git a/primitives/src/analytics.rs b/primitives/src/analytics.rs index 2fe8f6c30..d821be827 100644 --- a/primitives/src/analytics.rs +++ b/primitives/src/analytics.rs @@ -94,6 +94,12 @@ pub mod postgres { pub mod query; +// Query used for filtering analytics +// +/// # Examples: +/// ``` +#[doc = include_str!("../examples/analytics_query.rs")] +/// ``` #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct AnalyticsQuery { From 5b16739a39eea8e10296937a6cb317f6e3fe3828 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Wed, 3 Aug 2022 18:52:31 +0300 Subject: [PATCH 08/23] Fixed code examples for CreateCampaign again --- primitives/examples/analytics_query.rs | 8 ++++---- primitives/examples/create_campaign.rs | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/primitives/examples/analytics_query.rs b/primitives/examples/analytics_query.rs index 81bc596cf..c49489b4b 100644 --- a/primitives/examples/analytics_query.rs +++ b/primitives/examples/analytics_query.rs @@ -48,8 +48,8 @@ fn main() { &timeframe=week &start=420 &campaignId=0x936da01f9abd4d9d80c702af85c822a8 - &adUnit=Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f - &adSlot=QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR + &adUnit=QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR + &adSlot=Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f &adSlotType=legacy_300x100 &avertiser=0xDd589B43793934EF6Ad266067A0d1D4896b0dff0 &publisher=0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9 @@ -82,14 +82,14 @@ fn main() { assert_eq!( query.ad_unit, Some( - IPFS::from_str("Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f") + IPFS::from_str("QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR") .expect("should be valid") ) ); assert_eq!( query.ad_slot, Some( - IPFS::from_str("QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR") + IPFS::from_str("Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f") .expect("should be valid") ) ); diff --git a/primitives/examples/create_campaign.rs b/primitives/examples/create_campaign.rs index f250dd253..7ce46741f 100644 --- a/primitives/examples/create_campaign.rs +++ b/primitives/examples/create_campaign.rs @@ -24,12 +24,12 @@ fn main() { "validators":[ { "id":"0x80690751969B234697e9059e04ed72195c3507fa", - "fee":"2000000", + "fee":"3000000", "url":"http://localhost:8005" }, { "id":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", - "fee":"3000000", + "fee":"2000000", "url":"http://localhost:8006" } ], @@ -38,7 +38,7 @@ fn main() { "eventSubmission":{"allow":[]}, "targetingRules":[], "created":1612162800000_u64, - "active_to":4073414400000_u64 + "activeTo":4073414400000_u64 }); let create_campaign_json = @@ -71,12 +71,12 @@ fn main() { "validators":[ { "id":"0x80690751969B234697e9059e04ed72195c3507fa", - "fee":"2000000", + "fee":"3000000", "url":"http://localhost:8005" }, { "id":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", - "fee":"3000000", + "fee":"2000000", "url":"http://localhost:8006" } ], @@ -85,7 +85,7 @@ fn main() { "eventSubmission":{"allow":[]}, "targetingRules":[], "created":1612162800000_u64, - "active_to":4073414400000_u64 + "activeTo":4073414400000_u64 }); let create_campaign_json = From 94a9faae710fe16a2d74ae58820d275b5d1c5a11 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Thu, 4 Aug 2022 13:14:04 +0300 Subject: [PATCH 09/23] fixed AnalyticsQuery code example --- primitives/examples/analytics_query.rs | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/primitives/examples/analytics_query.rs b/primitives/examples/analytics_query.rs index c49489b4b..d330f3768 100644 --- a/primitives/examples/analytics_query.rs +++ b/primitives/examples/analytics_query.rs @@ -41,23 +41,8 @@ fn main() { // Query with all possible fields (publisher/advertiser/admin) { - let query_str = r#"limit=200 - &eventType=CLICK - &metric=paid - &segmentBy=country - &timeframe=week - &start=420 - &campaignId=0x936da01f9abd4d9d80c702af85c822a8 - &adUnit=QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR - &adSlot=Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f - &adSlotType=legacy_300x100 - &avertiser=0xDd589B43793934EF6Ad266067A0d1D4896b0dff0 - &publisher=0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9 - &hostname=localhost - &country=Bulgaria - &osName=Windows - &chains[0]=1&chains[1]=1337 - "#; + let query_str = "limit=200&eventType=CLICK&metric=paid&segmentBy=country&timeframe=week&start=2022-08-04+09:00:00.000000000+UTC&campaignId=0x936da01f9abd4d9d80c702af85c822a8&adUnit=QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR&adSlot=Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f&adSlotType=legacy_300x100&advertiser=0xDd589B43793934EF6Ad266067A0d1D4896b0dff0&publisher=0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9\ + &hostname=localhost&country=Bulgaria&osName=Windows&chains[0]=1&chains[1]=1337"; let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap(); assert_eq!(query.limit, 200); @@ -68,7 +53,7 @@ fn main() { query.time, Time { timeframe: Timeframe::Week, - start: DateHour::from_ymdh(2021, 12, 31, 22), + start: DateHour::from_ymdh(2022, 8, 4, 9), end: None, } ); From 849f2f3af82f616062fce25e1a6407c3a19e1e1f Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Thu, 4 Aug 2022 13:40:19 +0300 Subject: [PATCH 10/23] fixed wrong link --- sentry/src/routes/campaign.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/routes/campaign.rs b/sentry/src/routes/campaign.rs index 85be13036..fa731f895 100644 --- a/sentry/src/routes/campaign.rs +++ b/sentry/src/routes/campaign.rs @@ -144,7 +144,7 @@ pub async fn fetch_campaign_ids_for_channel( /// POST `/v5/campaign` /// -/// Expected request body: [`CreateCampaign`](`primitives::campaign::campaign_create::CreateCampaign`) +/// Expected request body: [`CreateCampaign`](`primitives::sentry::campaign_create::CreateCampaign`) /// /// Expected response: [Campaign](`primitives::Campaign`) pub async fn create_campaign( From 4837a9dfeec0ade00443f72c33b2c73f562c596b Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Thu, 4 Aug 2022 13:22:56 +0200 Subject: [PATCH 11/23] primitives - sentry - AnalyticsResponse --- primitives/src/sentry.rs | 6 +++ sentry/src/routes.rs | 18 +++++++ sentry/src/routes/analytics.rs | 14 ++++-- sentry/src/routes/routers.rs | 90 +++++++++++++++++++++------------- test_harness/src/lib.rs | 7 +-- 5 files changed, 92 insertions(+), 43 deletions(-) diff --git a/primitives/src/sentry.rs b/primitives/src/sentry.rs index b587e9634..c5040d970 100644 --- a/primitives/src/sentry.rs +++ b/primitives/src/sentry.rs @@ -319,6 +319,12 @@ pub struct FetchedAnalytics { pub segment: Option, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct AnalyticsResponse { + pub analytics: Vec, +} + /// The value of the requested analytics [`crate::analytics::Metric`]. #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[serde(untagged)] diff --git a/sentry/src/routes.rs b/sentry/src/routes.rs index e02bec99d..518ae3b94 100644 --- a/sentry/src/routes.rs +++ b/sentry/src/routes.rs @@ -290,6 +290,10 @@ //! //! Allowed keys: [`AllowedKey::Country`][primitives::analytics::query::AllowedKey::Country], [`AllowedKey::AdSlotType`][primitives::analytics::query::AllowedKey::AdSlotType] //! +//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery] +//! +//! Response: [`AnalyticsResponse`] +//! //! #### GET `/v5/analytics/for-publisher` (auth required) //! //! Returns all analytics where the currently authenticated address [`Auth.uid`](crate::Auth::uid) is a **publisher**. @@ -298,6 +302,10 @@ //! //! The route is handled by [`get_analytics()`]. //! +//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery] +//! +//! Response: [`AnalyticsResponse`] +//! //! #### GET `/v5/analytics/for-advertiser` (auth required) //! //! Returns all analytics where the currently authenticated address [`Auth.uid`](crate::Auth::uid) is an **advertiser**. @@ -306,6 +314,10 @@ //! //! The route is handled by [`get_analytics()`]. //! +//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery] +//! +//! Response: [`AnalyticsResponse`] +//! //! #### GET `/v5/analytics/for-admin` (auth required) //! //! Admin access to the analytics with no restrictions on the keys for filtering. @@ -316,6 +328,10 @@ //! //! The route is handled by [`get_analytics()`]. //! +//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery] +//! +//! Response: [`AnalyticsResponse`] +//! //! [`Adapter`]: adapter::Adapter //! [`Address`]: primitives::Address //! [`AllowedKey`]: primitives::analytics::query::AllowedKey @@ -334,6 +350,8 @@ //! [`check_access()`]: crate::access::check_access //! [`SuccessResponse`]: primitives::sentry::SuccessResponse //! [`ValidatorId`]: primitives::ValidatorId +//! [`AnalyticsResponse`]: primitives::sentry::AnalyticsResponse +//! [`AnalyticsQuery`]: primitives::analytics::AnalyticsQuery pub use analytics::analytics as get_analytics; diff --git a/sentry/src/routes/analytics.rs b/sentry/src/routes/analytics.rs index 5979dc76e..8681332c2 100644 --- a/sentry/src/routes/analytics.rs +++ b/sentry/src/routes/analytics.rs @@ -10,15 +10,19 @@ use crate::{ }; use adapter::client::Locked; use hyper::{Body, Request, Response}; -use primitives::analytics::{ - query::{AllowedKey, ALLOWED_KEYS}, - AnalyticsQuery, AuthenticateAs, +use primitives::{ + analytics::{ + query::{AllowedKey, ALLOWED_KEYS}, + AnalyticsQuery, AuthenticateAs, + }, + sentry::AnalyticsResponse, }; /// GET `/v5/analytics` routes /// with query parameters: [`primitives::analytics::AnalyticsQuery`]. /// -/// Returns `Vec<[FetchedAnalytics](primitives::sentry::FethedAnalytics)>` +/// Response: [`primitives::sentry::AnalyticsResponse`] +/// /// Analytics routes: /// - GET `/v5/analytics` /// - GET `/v5/analytics/for-publisher` @@ -84,7 +88,7 @@ pub async fn analytics( ) .await { - Ok(Ok(analytics)) => analytics, + Ok(Ok(analytics)) => AnalyticsResponse { analytics }, // Error getting the analytics Ok(Err(err)) => return Err(err.into()), // Timeout error diff --git a/sentry/src/routes/routers.rs b/sentry/src/routes/routers.rs index efa7412d7..12a8c75f1 100644 --- a/sentry/src/routes/routers.rs +++ b/sentry/src/routes/routers.rs @@ -404,7 +404,10 @@ mod analytics_router_test { query::{AllowedKey, Time}, AnalyticsQuery, Metric, OperatingSystem, Timeframe, }, - sentry::{DateHour, FetchedAnalytics, FetchedMetric, UpdateAnalytics, CLICK, IMPRESSION}, + sentry::{ + AnalyticsResponse, DateHour, FetchedAnalytics, FetchedMetric, UpdateAnalytics, CLICK, + IMPRESSION, + }, test_util::{ADVERTISER, DUMMY_CAMPAIGN, DUMMY_IPFS, IDS, LEADER, PUBLISHER, PUBLISHER_2}, UnifiedNum, }; @@ -691,8 +694,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![ @@ -749,8 +753,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![FetchedMetric::Count(2)], fetched_analytics @@ -796,8 +801,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![FetchedMetric::Count(1), FetchedMetric::Count(1)], @@ -846,8 +852,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![ @@ -899,8 +906,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![ @@ -974,8 +982,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![ @@ -1009,8 +1018,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![ @@ -1046,8 +1056,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( // Limit is 2 @@ -1082,8 +1093,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![ @@ -1115,8 +1127,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![FetchedMetric::Count(100)], @@ -1167,8 +1180,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![ @@ -1221,8 +1235,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![FetchedMetric::Count(69)], fetched_analytics @@ -1468,8 +1483,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![FetchedMetric::Count(1), FetchedMetric::Count(3)], @@ -1497,8 +1513,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![FetchedMetric::Count(1), FetchedMetric::Count(5)], @@ -1526,8 +1543,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![FetchedMetric::Count(1), FetchedMetric::Count(5)], fetched_analytics @@ -1576,8 +1594,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!(1, fetched_analytics.len()); assert_eq!( FetchedMetric::Count(1), @@ -1603,8 +1622,9 @@ mod analytics_router_test { .await .expect("Should get json"); - let fetched_analytics: Vec = - serde_json::from_slice(&json).expect("Should get analytics response"); + let fetched_analytics = serde_json::from_slice::(&json) + .expect("Should get analytics response") + .analytics; assert_eq!( vec![FetchedMetric::Count(69)], fetched_analytics diff --git a/test_harness/src/lib.rs b/test_harness/src/lib.rs index af4a2e480..ab53f2e43 100644 --- a/test_harness/src/lib.rs +++ b/test_harness/src/lib.rs @@ -232,8 +232,8 @@ mod tests { analytics::{query::Time, AnalyticsQuery, Metric, Timeframe}, balances::CheckedState, sentry::{ - campaign_create::CreateCampaign, AccountingResponse, DateHour, Event, EventType, - FetchedAnalytics, FetchedMetric, SuccessResponse, CLICK, IMPRESSION, + campaign_create::CreateCampaign, AccountingResponse, AnalyticsResponse, DateHour, + Event, EventType, FetchedAnalytics, FetchedMetric, SuccessResponse, CLICK, IMPRESSION, }, spender::Spender, test_util::{ @@ -2751,8 +2751,9 @@ mod tests { .send() .await .context("failed to get analytics")? - .json::>() + .json::() .await + .map(|response| response.analytics) .context("failed to deserialize json") } } From e7cec78151b441a939c0a8b5f4140e5ba1272504 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Thu, 4 Aug 2022 16:35:39 +0300 Subject: [PATCH 12/23] Added AnalyticsResponse examples --- primitives/examples/analytics_response.rs | 26 +++++++++++++++++++++++ primitives/src/sentry.rs | 6 ++++++ 2 files changed, 32 insertions(+) create mode 100644 primitives/examples/analytics_response.rs diff --git a/primitives/examples/analytics_response.rs b/primitives/examples/analytics_response.rs new file mode 100644 index 000000000..47a127f65 --- /dev/null +++ b/primitives/examples/analytics_response.rs @@ -0,0 +1,26 @@ +use primitives::sentry::AnalyticsResponse; +use serde_json::{from_value, json}; + +fn main() { + let json = json!({ + "analytics": [{ + "time": 1659592800, + "value": "3", + "segment": null + }, + { + "time": 1659592800, + "value": "10000000000", + "segment": null + }, + { + "time": 1659592800, + "value": "100000000", + "segment": "country" + }], + "totalPages": 1, + "page": 0 + }); + + assert!(from_value::(json).is_ok()); +} diff --git a/primitives/src/sentry.rs b/primitives/src/sentry.rs index c5040d970..601167811 100644 --- a/primitives/src/sentry.rs +++ b/primitives/src/sentry.rs @@ -319,6 +319,12 @@ pub struct FetchedAnalytics { pub segment: Option, } +// Response returned when getting Analytics - an array of FetchedAnalytics +// +/// # Examples: +/// ``` +#[doc = include_str!("../examples/analytics_response.rs")] +/// ``` #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AnalyticsResponse { From 48abedce81ee14f9797b65385b14e2021d149b4d Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Thu, 4 Aug 2022 16:42:15 +0300 Subject: [PATCH 13/23] Update sentry/src/routes/campaign.rs Co-authored-by: Lachezar Lechev <8925621+elpiel@users.noreply.github.com> --- sentry/src/routes/campaign.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry/src/routes/campaign.rs b/sentry/src/routes/campaign.rs index fa731f895..4e80b0370 100644 --- a/sentry/src/routes/campaign.rs +++ b/sentry/src/routes/campaign.rs @@ -146,7 +146,7 @@ pub async fn fetch_campaign_ids_for_channel( /// /// Expected request body: [`CreateCampaign`](`primitives::sentry::campaign_create::CreateCampaign`) /// -/// Expected response: [Campaign](`primitives::Campaign`) +/// Expected response: [`Campaign`](primitives::Campaign) pub async fn create_campaign( req: Request, app: &Application, From 91bf650a8dac0cdb46c267698d00452b0821d663 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Thu, 4 Aug 2022 18:53:54 +0300 Subject: [PATCH 14/23] removed docs folder --- sentry/docs/analytics/examples.js | 0 sentry/docs/analytics/glossary.md | 9 --- sentry/docs/analytics/tutorial.md | 14 ---- sentry/docs/create_campaign/examples.js | 61 ----------------- sentry/docs/create_campaign/examples.rs | 88 ------------------------- sentry/docs/create_campaign/glossary.md | 15 ----- sentry/docs/create_campaign/tutorial.md | 12 ---- sentry/docs/modify_campaign/examples.js | 0 sentry/docs/modify_campaign/glossary.md | 0 sentry/docs/modify_campaign/tutorial.md | 0 sentry/docs/post_events/examples.js | 0 sentry/docs/post_events/glossary.md | 15 ----- sentry/docs/post_events/tutorial.md | 9 --- 13 files changed, 223 deletions(-) delete mode 100644 sentry/docs/analytics/examples.js delete mode 100644 sentry/docs/analytics/glossary.md delete mode 100644 sentry/docs/analytics/tutorial.md delete mode 100644 sentry/docs/create_campaign/examples.js delete mode 100644 sentry/docs/create_campaign/examples.rs delete mode 100644 sentry/docs/create_campaign/glossary.md delete mode 100644 sentry/docs/create_campaign/tutorial.md delete mode 100644 sentry/docs/modify_campaign/examples.js delete mode 100644 sentry/docs/modify_campaign/glossary.md delete mode 100644 sentry/docs/modify_campaign/tutorial.md delete mode 100644 sentry/docs/post_events/examples.js delete mode 100644 sentry/docs/post_events/glossary.md delete mode 100644 sentry/docs/post_events/tutorial.md diff --git a/sentry/docs/analytics/examples.js b/sentry/docs/analytics/examples.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry/docs/analytics/glossary.md b/sentry/docs/analytics/glossary.md deleted file mode 100644 index c37a04876..000000000 --- a/sentry/docs/analytics/glossary.md +++ /dev/null @@ -1,9 +0,0 @@ -[`AllowedKey`](`primitives::query::AllowedKey`) - -[`Analytics`](`primitives::Analytics`) - -[`Auth`](crate::Auth) - -[`ResponseError`](`crate::response::ResponseError`) - -[`AnalyticsQuery`](`primitives::AnalyticsQuery`) \ No newline at end of file diff --git a/sentry/docs/analytics/tutorial.md b/sentry/docs/analytics/tutorial.md deleted file mode 100644 index bf9f33c1d..000000000 --- a/sentry/docs/analytics/tutorial.md +++ /dev/null @@ -1,14 +0,0 @@ -In order to call the route successfully you need to: -- GET `/analytics`: - - Use a valid `AnalyticsQuery`, the only keys that you can use are `AllowedKey::Country` and `AllowedKey::AdSlotType` - - If you are using `?segmentBy=...` query parameter it must also be an allowed key, otherwise an error will be returned -- GET `/analytics/for-publisher` (auth required): - - Returns all analytics where the currently authenticated address `Auth.uid` is a **publisher**. - - Use a valid `AnalyticsQuery` with no restrictons on the allowed keys that you can use. -- GET `/analytics/for-advertiser`: - - Returns all analytics where the currently authenticated address `Auth.uid` is a **advertiser**. - - Use a valid `AnalyticsQuery` with no restrictons on the allowed keys that you can use. -- GET `/analytics/for/admin`: - - Admin access to the analytics with no restrictions on the keys for filtering. - - Use a valid `AnalyticsQuery` with no restrictons on the allowed keys that you can use. -- If the request is successful your output will be an array with all the fetched entries which include the time, the value of the metric from the query and the `segment_by` field diff --git a/sentry/docs/create_campaign/examples.js b/sentry/docs/create_campaign/examples.js deleted file mode 100644 index f79e52182..000000000 --- a/sentry/docs/create_campaign/examples.js +++ /dev/null @@ -1,61 +0,0 @@ -const request = require('request'); - -const valid_campaign = { - id: null, - channel: { - leader: "0x80690751969B234697e9059e04ed72195c3507fa", - follower: "0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", - guardian: "0xe061E1EB461EaBE512759aa18A201B20Fe90631D", - token: "0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E", - nonce: 987654321, - }, - creator:"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F", - budget:100000000000, - validators: [ - { - id:"0x80690751969B234697e9059e04ed72195c3507fa", - fee:2000000, - url:"http://localhost:8005" - }, - { - id:"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", - fee:3000000, - url:"http://localhost:8006" - } - ], - title:"Dummy Campaign", - pricingBounds:{ - CLICK: {min: 0,max:0 }, - IMPRESSION:{min:1,max:10}}, - eventSubmission:{allow:[]}, - targetingRules:[], - created:1612162800000, - active_to:4073414400000 -} -const chain = { - chain_id: 1, - rpc: "http://dummy.com", - outpace: "0x0000000000000000000000000000000000000000" -}; - -// Valid Request -const auth = { - era: 0, - uid: "0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F", - chain, -}; - - -request({ - url: "http://localhost:8005/v5/campaign", - method: "POST", - json: true, - body: JSON.stringify(valid_campaign) -}, (err, res, body) => { - console.log(body); // should be a Campaign object -}); - -// Request with a broken campaign - - -// Request from a different creator \ No newline at end of file diff --git a/sentry/docs/create_campaign/examples.rs b/sentry/docs/create_campaign/examples.rs deleted file mode 100644 index 5ae7f3b1e..000000000 --- a/sentry/docs/create_campaign/examples.rs +++ /dev/null @@ -1,88 +0,0 @@ -fn main() { - let valid_campaign = json!("{ - \"id\":null, - \"channel\":{ - \"leader\":\"0x80690751969B234697e9059e04ed72195c3507fa\", - \"follower\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", - \"guardian\":\"0xe061E1EB461EaBE512759aa18A201B20Fe90631D\", - \"token\":\"0x2BCaf6968aEC8A3b5126FBfAb5Fd419da6E8AD8E\", - \"nonce\":\"987654321\" - }, - \"creator\":\"0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F\", - \"budget\":\"100000000000\", - \"validators\":[ - { - \"id\":\"0x80690751969B234697e9059e04ed72195c3507fa\", - \"fee\":\"2000000\", - \"url\":\"http://localhost:8005\" - }, - { - \"id\":\"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7\", - \"fee\":\"3000000\", - \"url\":\"http://localhost:8006\" - } - ], - \"title\":\"Dummy Campaign\", - \"pricingBounds\":{\"CLICK\":{\"min\":\"0\",\"max\":\"0\"},\"IMPRESSION\":{\"min\":\"1\",\"max\":\"10\"}}, - \"eventSubmission\":{\"allow\":[]}, - \"targetingRules\":[], - \"created\":1612162800000, - \"active_to\":4073414400000 - }"); - - let not_a_valid_campaign = json!("{ - \"id\":null, - \"channel\": 1234567890, - }"); - - // Valid request - { - let auth = Auth { - era: 0, - uid: ValidatorId::from("0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F"), - chain: channel_context.chain.clone(), - }; - - let req = Request::builder() - .extension(auth) - .body(valid_campaign) - .expect("Should build Request"); - - // Should return a Campaign - let res = make_request("POST", "/v5/campaign/", req); - } - - // Request with an invalid campaign - { - let auth = Auth { - era: 0, - uid: ValidatorId::from("0xaCBaDA2d5830d1875ae3D2de207A1363B316Df2F"), - chain: channel_context.chain.clone(), - }; - - let req = Request::builder() - .extension(auth) - .body(not_a_valid_campaign) - .expect("Should build Request"); - - // ResponseError::FailedValidation - let res = make_request("POST", "/v5/campaign/", req); - } - - // Request not sent by creator - { - let auth = Auth { - era: 0, - uid: ValidatorId::from("0x0000000000000000000000000000000000000000"), // not the creator - chain: channel_context.chain.clone(), - }; - - let req = Request::builder() - .extension(auth) - .body(valid_campaign) - .expect("Should build Request"); - - // ResponseError::Forbidden - let res = make_request("POST", "/v5/campaign/", req); - } -} \ No newline at end of file diff --git a/sentry/docs/create_campaign/glossary.md b/sentry/docs/create_campaign/glossary.md deleted file mode 100644 index e5ab12463..000000000 --- a/sentry/docs/create_campaign/glossary.md +++ /dev/null @@ -1,15 +0,0 @@ -[`Campaign`](`primitives::campaign::Campaign`) - -[`CreateCampaign`](`primitives::sentry::campaign_create::CreateCampaign`) - -[`Channel`](`primitives::Channel`) - -[`Spendable`](`primitives::spender::Spendable`) - -[`CampaignId`](`primitives::CampaignId`) - -[`Validator`](`primitives::campaign_validator::Validator`) - -[`Auth`](`crate::application::Auth`) - -[`ResponseError`](`crate::response::ResponseError`) \ No newline at end of file diff --git a/sentry/docs/create_campaign/tutorial.md b/sentry/docs/create_campaign/tutorial.md deleted file mode 100644 index 8d1543c5e..000000000 --- a/sentry/docs/create_campaign/tutorial.md +++ /dev/null @@ -1,12 +0,0 @@ -In order to call the route successfully you need to: -- Have a valid `CreateCampaign` struct as the request body, which is the same as the `Campaign` struct except that the `id` field is optional. (`CampaignId` is generated on creation) -(TODO: Explain when and why an id will be provided) -- Request must be sent by the `Campaign.creator`. (`Auth.uid == Campaign.creator`) -- The campaign must pass the validations made by `Validator.validate()` which include: - - Ensuring the `Channel`'s validators include the adapter identity - - Ensuring the `active.to` field is a valid future date - - Ensuring the `Campaign.validators`, `Campaign.creator` and `Channel.token` are whitelisted - - Ensuring the campaign budget is above the minimum campaign budget configured - - Ensuring the validator fee is greater than the minimum configured fee -- If any of the requirements are not met a `ResponseError` will be returned -- On a successful request your response will be a serialied `Campaign` struct \ No newline at end of file diff --git a/sentry/docs/modify_campaign/examples.js b/sentry/docs/modify_campaign/examples.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry/docs/modify_campaign/glossary.md b/sentry/docs/modify_campaign/glossary.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry/docs/modify_campaign/tutorial.md b/sentry/docs/modify_campaign/tutorial.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry/docs/post_events/examples.js b/sentry/docs/post_events/examples.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry/docs/post_events/glossary.md b/sentry/docs/post_events/glossary.md deleted file mode 100644 index ebcc950d8..000000000 --- a/sentry/docs/post_events/glossary.md +++ /dev/null @@ -1,15 +0,0 @@ -[`Event`](`primitives::Event`) - -[`EventType`](`primitives::EventType`) - -[`Campaign`](`primitives::Campaign`) - -[`check_access()`](`sentry::access::check_access`) - -[`Rule`](`primitives::event_submission::Rule`) - -[`apply_rule()`](`sentry::access::apply_rule`) - -[`EventError`](`sentry::routes::campaign::insert_events::EventError`) - -[`record()`](`sentry::analytics::record`) \ No newline at end of file diff --git a/sentry/docs/post_events/tutorial.md b/sentry/docs/post_events/tutorial.md deleted file mode 100644 index 4b86cf60a..000000000 --- a/sentry/docs/post_events/tutorial.md +++ /dev/null @@ -1,9 +0,0 @@ -In order to call the route successfully you need to: -- You must send an authenticated request with a body which is an array of events in the - form of `{ events: [ ... ] }` -- The events must pass all the rules in `check_access(...)` that include: - - The campaign shouldn't be expired - - The session shouldn't be from a forbidden country or referrer - - The events should apply all applicable access rules - either the ones provided in `Campaign.event_submission` or the default ones (uid = campaign.creator). The rule uid's should include the `Auth.uid` to make a rule applicable. If a Rule is found which has a `rate_limit` field, then rate limit is applied using `apply_rule(...)` -- Publisher payouts and validator fees are calculated, this step can fail if the campaign has no remaining bduget. If remaining is negative after Redis/Postgres are updated an `EventError` will be thrown -- Successfully paid events will be recorded in analytics (see [`record(...)`](`sentry::analytics::record`)) From 653fd6453ee89a2db11f34daba2e3974e32ab9d5 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Thu, 4 Aug 2022 18:54:48 +0300 Subject: [PATCH 15/23] added examples to cargo toml --- primitives/Cargo.toml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index f8643ff38..b4c8a0471 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -36,6 +36,18 @@ required-features = ["test-util"] name = "create_campaign" required-features = ["test-util"] +[[example]] +name = "modify_campaign" +required-features = ["test-util"] + +[[example]] +name = "analytics_query" +required-features = ["test-util"] + +[[example]] +name = "analytics_response" +required-features = ["test-util"] + [dependencies] # (De)Serialization serde = { version = "1.0", features = ["derive"] } From 4cc94392d12f9e41fff312073cda2ea55317ec52 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 5 Aug 2022 09:45:47 +0200 Subject: [PATCH 16/23] remove unused doc files --- sentry/docs/analytics/examples.js | 0 sentry/docs/analytics/glossary.md | 9 --------- sentry/docs/analytics/tutorial.md | 14 -------------- sentry/docs/modify_campaign/examples.js | 0 sentry/docs/modify_campaign/glossary.md | 0 sentry/docs/modify_campaign/tutorial.md | 0 sentry/docs/post_events/examples.js | 0 sentry/docs/post_events/glossary.md | 15 --------------- sentry/docs/post_events/tutorial.md | 9 --------- 9 files changed, 47 deletions(-) delete mode 100644 sentry/docs/analytics/examples.js delete mode 100644 sentry/docs/analytics/glossary.md delete mode 100644 sentry/docs/analytics/tutorial.md delete mode 100644 sentry/docs/modify_campaign/examples.js delete mode 100644 sentry/docs/modify_campaign/glossary.md delete mode 100644 sentry/docs/modify_campaign/tutorial.md delete mode 100644 sentry/docs/post_events/examples.js delete mode 100644 sentry/docs/post_events/glossary.md delete mode 100644 sentry/docs/post_events/tutorial.md diff --git a/sentry/docs/analytics/examples.js b/sentry/docs/analytics/examples.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry/docs/analytics/glossary.md b/sentry/docs/analytics/glossary.md deleted file mode 100644 index c37a04876..000000000 --- a/sentry/docs/analytics/glossary.md +++ /dev/null @@ -1,9 +0,0 @@ -[`AllowedKey`](`primitives::query::AllowedKey`) - -[`Analytics`](`primitives::Analytics`) - -[`Auth`](crate::Auth) - -[`ResponseError`](`crate::response::ResponseError`) - -[`AnalyticsQuery`](`primitives::AnalyticsQuery`) \ No newline at end of file diff --git a/sentry/docs/analytics/tutorial.md b/sentry/docs/analytics/tutorial.md deleted file mode 100644 index bf9f33c1d..000000000 --- a/sentry/docs/analytics/tutorial.md +++ /dev/null @@ -1,14 +0,0 @@ -In order to call the route successfully you need to: -- GET `/analytics`: - - Use a valid `AnalyticsQuery`, the only keys that you can use are `AllowedKey::Country` and `AllowedKey::AdSlotType` - - If you are using `?segmentBy=...` query parameter it must also be an allowed key, otherwise an error will be returned -- GET `/analytics/for-publisher` (auth required): - - Returns all analytics where the currently authenticated address `Auth.uid` is a **publisher**. - - Use a valid `AnalyticsQuery` with no restrictons on the allowed keys that you can use. -- GET `/analytics/for-advertiser`: - - Returns all analytics where the currently authenticated address `Auth.uid` is a **advertiser**. - - Use a valid `AnalyticsQuery` with no restrictons on the allowed keys that you can use. -- GET `/analytics/for/admin`: - - Admin access to the analytics with no restrictions on the keys for filtering. - - Use a valid `AnalyticsQuery` with no restrictons on the allowed keys that you can use. -- If the request is successful your output will be an array with all the fetched entries which include the time, the value of the metric from the query and the `segment_by` field diff --git a/sentry/docs/modify_campaign/examples.js b/sentry/docs/modify_campaign/examples.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry/docs/modify_campaign/glossary.md b/sentry/docs/modify_campaign/glossary.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry/docs/modify_campaign/tutorial.md b/sentry/docs/modify_campaign/tutorial.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry/docs/post_events/examples.js b/sentry/docs/post_events/examples.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/sentry/docs/post_events/glossary.md b/sentry/docs/post_events/glossary.md deleted file mode 100644 index ebcc950d8..000000000 --- a/sentry/docs/post_events/glossary.md +++ /dev/null @@ -1,15 +0,0 @@ -[`Event`](`primitives::Event`) - -[`EventType`](`primitives::EventType`) - -[`Campaign`](`primitives::Campaign`) - -[`check_access()`](`sentry::access::check_access`) - -[`Rule`](`primitives::event_submission::Rule`) - -[`apply_rule()`](`sentry::access::apply_rule`) - -[`EventError`](`sentry::routes::campaign::insert_events::EventError`) - -[`record()`](`sentry::analytics::record`) \ No newline at end of file diff --git a/sentry/docs/post_events/tutorial.md b/sentry/docs/post_events/tutorial.md deleted file mode 100644 index 4b86cf60a..000000000 --- a/sentry/docs/post_events/tutorial.md +++ /dev/null @@ -1,9 +0,0 @@ -In order to call the route successfully you need to: -- You must send an authenticated request with a body which is an array of events in the - form of `{ events: [ ... ] }` -- The events must pass all the rules in `check_access(...)` that include: - - The campaign shouldn't be expired - - The session shouldn't be from a forbidden country or referrer - - The events should apply all applicable access rules - either the ones provided in `Campaign.event_submission` or the default ones (uid = campaign.creator). The rule uid's should include the `Auth.uid` to make a rule applicable. If a Rule is found which has a `rate_limit` field, then rate limit is applied using `apply_rule(...)` -- Publisher payouts and validator fees are calculated, this step can fail if the campaign has no remaining bduget. If remaining is negative after Redis/Postgres are updated an `EventError` will be thrown -- Successfully paid events will be recorded in analytics (see [`record(...)`](`sentry::analytics::record`)) From 79e49903170f011c3f796ed71f1920a561bc9fc4 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 5 Aug 2022 09:57:47 +0200 Subject: [PATCH 17/23] example - analytics_response - remove pagination --- primitives/examples/analytics_response.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/primitives/examples/analytics_response.rs b/primitives/examples/analytics_response.rs index 47a127f65..c5f2cbb92 100644 --- a/primitives/examples/analytics_response.rs +++ b/primitives/examples/analytics_response.rs @@ -17,9 +17,7 @@ fn main() { "time": 1659592800, "value": "100000000", "segment": "country" - }], - "totalPages": 1, - "page": 0 + }] }); assert!(from_value::(json).is_ok()); From 9ff588a78f57c49a6c2ec6598bf5c9baf8f3245d Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 5 Aug 2022 11:06:15 +0200 Subject: [PATCH 18/23] sentry - Improve docs & create InsertEventsRequest --- primitives/Cargo.toml | 13 ++++++- primitives/examples/analytics_query.rs | 12 +++++- primitives/examples/create_campaign.rs | 12 ++++-- primitives/examples/modify_campaign.rs | 1 - primitives/src/sentry.rs | 14 +++++-- sentry/src/routes.rs | 53 ++++++++++++++++++++------ sentry/src/routes/campaign.rs | 22 ++++------- 7 files changed, 90 insertions(+), 37 deletions(-) diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index f8643ff38..4dc4868db 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -21,8 +21,10 @@ postgres = ["bytes", "tokio-postgres", "deadpool-postgres"] test-util = [] [[example]] -name = "channel_list_query" -required-features = ["test-util"] +name = "analytics_query" + +[[example]] +name = "analytics_response" [[example]] name = "campaign_list_query" @@ -32,10 +34,17 @@ required-features = ["test-util"] name = "campaign_list_response" required-features = ["test-util"] +[[example]] +name = "channel_list_query" +required-features = ["test-util"] + [[example]] name = "create_campaign" required-features = ["test-util"] +[[example]] +name = "modify_campaign" + [dependencies] # (De)Serialization serde = { version = "1.0", features = ["derive"] } diff --git a/primitives/examples/analytics_query.rs b/primitives/examples/analytics_query.rs index d330f3768..5fd4d98ef 100644 --- a/primitives/examples/analytics_query.rs +++ b/primitives/examples/analytics_query.rs @@ -41,8 +41,16 @@ fn main() { // Query with all possible fields (publisher/advertiser/admin) { - let query_str = "limit=200&eventType=CLICK&metric=paid&segmentBy=country&timeframe=week&start=2022-08-04+09:00:00.000000000+UTC&campaignId=0x936da01f9abd4d9d80c702af85c822a8&adUnit=QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR&adSlot=Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f&adSlotType=legacy_300x100&advertiser=0xDd589B43793934EF6Ad266067A0d1D4896b0dff0&publisher=0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9\ - &hostname=localhost&country=Bulgaria&osName=Windows&chains[0]=1&chains[1]=1337"; + let query_str = "limit=200&eventType=CLICK&metric=paid&segmentBy=country\ + &timeframe=week&start=2022-08-04+09:00:00.000000000+UTC\ + &campaignId=0x936da01f9abd4d9d80c702af85c822a8\ + &adUnit=QmcUVX7fvoLMM93uN2bD3wGTH8MXSxeL8hojYfL2Lhp7mR\ + &adSlot=Qmasg8FrbuSQpjFu3kRnZF9beg8rEBFrqgi1uXDRwCbX5f\ + &adSlotType=legacy_300x100\ + &advertiser=0xDd589B43793934EF6Ad266067A0d1D4896b0dff0\ + &publisher=0xE882ebF439207a70dDcCb39E13CA8506c9F45fD9\ + &hostname=localhost&country=Bulgaria&osName=Windows\ + &chains[0]=1&chains[1]=1337"; let query: AnalyticsQuery = serde_qs::from_str(query_str).unwrap(); assert_eq!(query.limit, 200); diff --git a/primitives/examples/create_campaign.rs b/primitives/examples/create_campaign.rs index 7ce46741f..3b742ff50 100644 --- a/primitives/examples/create_campaign.rs +++ b/primitives/examples/create_campaign.rs @@ -3,15 +3,15 @@ use serde_json::json; use std::str::FromStr; fn main() { - // CreateCampaign in an HTTP request + // CreateCampaign in an HTTP request. + // A CampaignId will be randomly generated for the newly created Campaign. { let create_campaign = CreateCampaign::from_campaign_erased(DUMMY_CAMPAIGN.clone(), None); - let create_campaign_str = + let _create_campaign_str = serde_json::to_string(&create_campaign).expect("should serialize"); let create_campaign_json = json!({ - "id":null, "channel":{ "leader":"0x80690751969B234697e9059e04ed72195c3507fa", "follower":"0xf3f583AEC5f7C030722Fe992A5688557e1B86ef7", @@ -34,7 +34,10 @@ fn main() { } ], "title":"Dummy Campaign", - "pricingBounds":{"CLICK":{"min":"0","max":"0"},"IMPRESSION":{"min":"1","max":"10"}}, + "pricingBounds":{ + "CLICK":{"min":"0","max":"0"}, + "IMPRESSION":{"min":"1","max":"10"} + }, "eventSubmission":{"allow":[]}, "targetingRules":[], "created":1612162800000_u64, @@ -43,6 +46,7 @@ fn main() { let create_campaign_json = serde_json::to_string(&create_campaign_json).expect("should serialize"); + let deserialized: CreateCampaign = serde_json::from_str(&create_campaign_json).expect("should deserialize"); diff --git a/primitives/examples/modify_campaign.rs b/primitives/examples/modify_campaign.rs index 3b663f9ca..8087c0c03 100644 --- a/primitives/examples/modify_campaign.rs +++ b/primitives/examples/modify_campaign.rs @@ -1,6 +1,5 @@ use primitives::{sentry::campaign_modify::ModifyCampaign, unified_num::FromWhole, UnifiedNum}; use serde_json::json; -use std::str::FromStr; fn main() { { diff --git a/primitives/src/sentry.rs b/primitives/src/sentry.rs index 601167811..f14d56f42 100644 --- a/primitives/src/sentry.rs +++ b/primitives/src/sentry.rs @@ -319,9 +319,10 @@ pub struct FetchedAnalytics { pub segment: Option, } -// Response returned when getting Analytics - an array of FetchedAnalytics -// -/// # Examples: +/// Response returned when getting Analytics which returns the [`FetchedAnalytics`]. +/// +/// # Examples +/// /// ``` #[doc = include_str!("../examples/analytics_response.rs")] /// ``` @@ -651,6 +652,13 @@ pub struct ValidationErrorResponse { pub validation: Vec, } +/// Request body for posting new [`Event`]s to a [`Campaign`](crate::Campaign). +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct InsertEventsRequest { + pub events: Vec, +} + pub mod channel_list { use crate::{ChainId, Channel, ValidatorId}; use serde::{Deserialize, Serialize}; diff --git a/sentry/src/routes.rs b/sentry/src/routes.rs index 518ae3b94..63ca7ee9c 100644 --- a/sentry/src/routes.rs +++ b/sentry/src/routes.rs @@ -106,7 +106,8 @@ //! //! Request body (json): [`ValidatorMessagesCreateRequest`](primitives::sentry::ValidatorMessagesCreateRequest) //! -//! Example: +//! ##### Examples: +//! //! ```json //! { //! "messages": [ @@ -231,7 +232,7 @@ //! **Authentication is required** to validate [`Campaign.creator`](primitives::Campaign::creator) == [`Auth.uid`](crate::Auth::uid) //! //! It will make sure the `Channel` is created if new and it will update -//! the spendable amount using the [`Adapter`]`::get_deposit()`. +//! the spendable amount using the [`Adapter.get_deposit()`](adapter::client::Locked::get_deposit). //! //! The route is handled by [`campaign::create_campaign()`]. //! @@ -239,6 +240,12 @@ //! //! Response: [`Campaign`] //! +//! ##### Examples +//! +//! ``` +#![doc = include_str!("../../primitives/examples/create_campaign.rs")] +//! ``` +//! //! #### POST `/v5/campaign/:id` (auth required) //! //! Modify the [`Campaign`]. Request must be sent by the [`Campaign.creator`](primitives::Campaign::creator). @@ -251,6 +258,12 @@ //! //! Response: [`Campaign`] //! +//! ##### Examples +//! +//! ``` +#![doc = include_str!("../../primitives/examples/modify_campaign.rs")] +//! ``` +//! //! #### POST `/v5/campaign/:id/events` //! //! Add new [`Event`]s (`IMPRESSION`s & `CLICK`s) to the [`Campaign`]. @@ -258,15 +271,7 @@ //! //! The route is handled by [`campaign::insert_events::handle_route()`]. //! -//! Request body (json): -//! -//! ```json -//! { -//! "events": [ -//! // Events -//! ] -//! } -//! ``` +//! Request body (json): [`InsertEventsRequest`](primitives::sentry::InsertEventsRequest) //! //! Response: [`SuccessResponse`] //! @@ -294,6 +299,20 @@ //! //! Response: [`AnalyticsResponse`] //! +//! ##### Examples +//! +//! Query: +//! +//! ``` +#![doc = include_str!("../../primitives/examples/analytics_query.rs")] +//! ``` +//! +//! Response: +//! +//! ``` +#![doc = include_str!("../../primitives/examples/analytics_response.rs")] +//! ``` +//! //! #### GET `/v5/analytics/for-publisher` (auth required) //! //! Returns all analytics where the currently authenticated address [`Auth.uid`](crate::Auth::uid) is a **publisher**. @@ -306,6 +325,10 @@ //! //! Response: [`AnalyticsResponse`] //! +//! ##### Examples +//! +//! See [GET `/v5/analytics`](#get-v5analytics) +//! //! #### GET `/v5/analytics/for-advertiser` (auth required) //! //! Returns all analytics where the currently authenticated address [`Auth.uid`](crate::Auth::uid) is an **advertiser**. @@ -318,6 +341,10 @@ //! //! Response: [`AnalyticsResponse`] //! +//! ##### Examples +//! +//! See [GET `/v5/analytics`](#get-v5analytics) +//! //! #### GET `/v5/analytics/for-admin` (auth required) //! //! Admin access to the analytics with no restrictions on the keys for filtering. @@ -332,6 +359,10 @@ //! //! Response: [`AnalyticsResponse`] //! +//! ##### Examples +//! +//! See [GET `/v5/analytics`](#get-v5analytics) +//! //! [`Adapter`]: adapter::Adapter //! [`Address`]: primitives::Address //! [`AllowedKey`]: primitives::analytics::query::AllowedKey diff --git a/sentry/src/routes/campaign.rs b/sentry/src/routes/campaign.rs index 4e80b0370..9e32c4a99 100644 --- a/sentry/src/routes/campaign.rs +++ b/sentry/src/routes/campaign.rs @@ -593,8 +593,6 @@ pub mod update_campaign { pub mod insert_events { - use std::collections::HashMap; - use crate::{ access::{self, check_access}, analytics, @@ -608,7 +606,7 @@ pub mod insert_events { use hyper::{Body, Request, Response}; use primitives::{ balances::{Balances, CheckedState, OverflowError}, - sentry::{Event, SuccessResponse}, + sentry::{Event, SuccessResponse, InsertEventsRequest}, Address, Campaign, CampaignId, ChainOf, DomainError, UnifiedNum, ValidatorDesc, }; use slog::{error, Logger}; @@ -642,9 +640,9 @@ pub mod insert_events { /// POST `/v5/campaign/:id/events` /// - /// The expected request body is `Vec<[Event](primitives::Event)>` + /// Request body (json): [`InsertEventsRequest`] /// - /// The expected Response is [`SuccessResponse`](primitives::sentry::SuccessResponse) + /// Response: [`SuccessResponse`] pub async fn handle_route( req: Request, app: &Application, @@ -663,17 +661,13 @@ pub mod insert_events { .expect("request should have a Campaign loaded"); let body_bytes = hyper::body::to_bytes(req_body).await?; - let mut request_body = serde_json::from_slice::>>(&body_bytes)?; - - let events = request_body - .remove("events") - .ok_or_else(|| ResponseError::BadRequest("invalid request".to_string()))?; + let request_body = serde_json::from_slice::(&body_bytes)?; - let processed = process_events(app, auth, session, campaign_context, events).await?; + process_events(app, auth, session, campaign_context, request_body.events).await?; Ok(Response::builder() .header("Content-type", "application/json") - .body(serde_json::to_string(&SuccessResponse { success: processed })?.into()) + .body(serde_json::to_string(&SuccessResponse { success: true })?.into()) .unwrap()) } @@ -683,7 +677,7 @@ pub mod insert_events { session: &Session, campaign_context: &ChainOf, events: Vec, - ) -> Result { + ) -> Result<(), ResponseError> { let campaign = &campaign_context.context; // handle events - check access @@ -732,7 +726,7 @@ pub mod insert_events { events_success, ); - Ok(true) + Ok(()) } /// Max retries is `5` after which an error logging message will be recorded. From a1ce814a84759c900556ec51b40421c579a81ba5 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 5 Aug 2022 11:07:44 +0200 Subject: [PATCH 19/23] test_harness - use InsertEventsRequest --- test_harness/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test_harness/src/lib.rs b/test_harness/src/lib.rs index ab53f2e43..e08494e43 100644 --- a/test_harness/src/lib.rs +++ b/test_harness/src/lib.rs @@ -233,7 +233,7 @@ mod tests { balances::CheckedState, sentry::{ campaign_create::CreateCampaign, AccountingResponse, AnalyticsResponse, DateHour, - Event, EventType, FetchedAnalytics, FetchedMetric, SuccessResponse, CLICK, IMPRESSION, + Event, EventType, FetchedAnalytics, FetchedMetric, SuccessResponse, CLICK, IMPRESSION, InsertEventsRequest, }, spender::Spender, test_util::{ @@ -2692,9 +2692,9 @@ mod tests { .join(&format!("v5/campaign/{}/events", campaign_context.context)) .expect("valid endpoint"); - let request_body = vec![("events".to_string(), events)] - .into_iter() - .collect::>(); + let request_body = InsertEventsRequest { + events: events.to_vec(), + }; let auth_token = sentry .adapter From f0f1b0316aa24f09bd1fd9d9d368712595d0bf76 Mon Sep 17 00:00:00 2001 From: Simeon Nakov Date: Fri, 5 Aug 2022 13:14:28 +0300 Subject: [PATCH 20/23] Pull request fixes --- primitives/examples/analytics_response.rs | 4 +--- sentry/src/routes/analytics.rs | 2 +- sentry/src/routes/campaign.rs | 20 +++++++++++++------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/primitives/examples/analytics_response.rs b/primitives/examples/analytics_response.rs index 47a127f65..c5f2cbb92 100644 --- a/primitives/examples/analytics_response.rs +++ b/primitives/examples/analytics_response.rs @@ -17,9 +17,7 @@ fn main() { "time": 1659592800, "value": "100000000", "segment": "country" - }], - "totalPages": 1, - "page": 0 + }] }); assert!(from_value::(json).is_ok()); diff --git a/sentry/src/routes/analytics.rs b/sentry/src/routes/analytics.rs index 8681332c2..ac0d98036 100644 --- a/sentry/src/routes/analytics.rs +++ b/sentry/src/routes/analytics.rs @@ -19,7 +19,7 @@ use primitives::{ }; /// GET `/v5/analytics` routes -/// with query parameters: [`primitives::analytics::AnalyticsQuery`]. +/// Request query parameters: [`primitives::analytics::AnalyticsQuery`]. /// /// Response: [`primitives::sentry::AnalyticsResponse`] /// diff --git a/sentry/src/routes/campaign.rs b/sentry/src/routes/campaign.rs index fa731f895..fec542dbb 100644 --- a/sentry/src/routes/campaign.rs +++ b/sentry/src/routes/campaign.rs @@ -144,9 +144,9 @@ pub async fn fetch_campaign_ids_for_channel( /// POST `/v5/campaign` /// -/// Expected request body: [`CreateCampaign`](`primitives::sentry::campaign_create::CreateCampaign`) +/// Request body (json): [`CreateCampaign`](`primitives::sentry::campaign_create::CreateCampaign`) /// -/// Expected response: [Campaign](`primitives::Campaign`) +/// Response: [Campaign](`primitives::Campaign`) pub async fn create_campaign( req: Request, app: &Application, @@ -374,10 +374,10 @@ pub mod update_campaign { /// POST `/v5/campaign/:id` (auth required) /// - /// Request body is [`ModifyCampaign`](`primitives::sentry::campaign_modify::ModifyCampaign`) - /// which consists of all the editable fields of the [`Campaign`] + /// Request body (json): [`ModifyCampaign`](`primitives::sentry::campaign_modify::ModifyCampaign`) + /// consists of all of the editable fields of the [`Campaign`] /// - /// Returns the updated [`Campaign`] serialized + /// Response[`Campaign`](`primitives::Campaign`) /// /// Ensures that the remaining funds for all campaigns <= total remaining funds (total deposited - total spent) /// @@ -642,9 +642,15 @@ pub mod insert_events { /// POST `/v5/campaign/:id/events` /// - /// The expected request body is `Vec<[Event](primitives::Event)>` + /// Request body (json): /// - /// The expected Response is [`SuccessResponse`](primitives::sentry::SuccessResponse) + /// ```json + /// { + /// "events": [[`Event`](`primitives::Event`)] + /// } + /// ``` + /// + /// Response: [`SuccessResponse`](primitives::sentry::SuccessResponse) pub async fn handle_route( req: Request, app: &Application, From ae42e68f30f7297f135c284c5486d655b6c6ee09 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 5 Aug 2022 12:29:42 +0200 Subject: [PATCH 21/23] rustfmt --- sentry/src/routes/campaign.rs | 2 +- test_harness/src/lib.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sentry/src/routes/campaign.rs b/sentry/src/routes/campaign.rs index 9e32c4a99..d725e5430 100644 --- a/sentry/src/routes/campaign.rs +++ b/sentry/src/routes/campaign.rs @@ -606,7 +606,7 @@ pub mod insert_events { use hyper::{Body, Request, Response}; use primitives::{ balances::{Balances, CheckedState, OverflowError}, - sentry::{Event, SuccessResponse, InsertEventsRequest}, + sentry::{Event, InsertEventsRequest, SuccessResponse}, Address, Campaign, CampaignId, ChainOf, DomainError, UnifiedNum, ValidatorDesc, }; use slog::{error, Logger}; diff --git a/test_harness/src/lib.rs b/test_harness/src/lib.rs index e08494e43..aba8def05 100644 --- a/test_harness/src/lib.rs +++ b/test_harness/src/lib.rs @@ -233,7 +233,8 @@ mod tests { balances::CheckedState, sentry::{ campaign_create::CreateCampaign, AccountingResponse, AnalyticsResponse, DateHour, - Event, EventType, FetchedAnalytics, FetchedMetric, SuccessResponse, CLICK, IMPRESSION, InsertEventsRequest, + Event, EventType, FetchedAnalytics, FetchedMetric, InsertEventsRequest, + SuccessResponse, CLICK, IMPRESSION, }, spender::Spender, test_util::{ From 68f667a35643152b90290b6c7d957ed83fc4cc50 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 5 Aug 2022 12:43:58 +0200 Subject: [PATCH 22/23] remove .DS_Store files --- .DS_Store | Bin 10244 -> 0 bytes sentry/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 sentry/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 95dad683208efacab44c02b5a61e46b1a3fcf30f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10244 zcmeHNziSjh6n=BL{2+<(suXImXrUHLEF=~Z*KmRef{=g+g86ab5|YcEm&*maD^%PCqDhk`T38E~v50>`5U~(z3qjED&Cc$^hNtxtBnaIjyC`w01|DHA{gM~4wm!G3zBFAw8&CrSaHY;LE=|)SO<2*X?_S(O zV%Qlx)P3!N#?a7Dp;$+fY$frSy^8y((gICVk#aO;<6*!Wv9OSM?yetQ*;#w*DP)m_%>TYei-AM-D6H|iyh z4>vx2JsaW>eMm-x)O4b*tf*G z7khylFt!@%)LOH7-@+ zak=6ca11yG90QI4$H4w&pe=yz@%jIy? T(K&;a=ehiULFUWEAp(3%?Ow_J>8c~!1t)2FX}ab=Zd^JO)MKP^t)kESo57yYrtuVYtt z&H7W`#t^@%HRY|HCD+GWPjfFHUsw70TilE0mDRR-*W0SM7r35@F<=ZB1G~xqdNxaX zC}^uOUtK<1ThLa0|wi*M*K%0S~+>W^aU-r-c?IgQ128@B7V!$QY zESun!w6}I%j(e?#-auJ6t`^*+;3BqS#BwV>gGPbf^93*{)`GA=?2kaC!4_lSPZ{_I DU{XvO From f80f20be0be394b9fb9809bcf41941cde09504f6 Mon Sep 17 00:00:00 2001 From: Lachezar Lechev Date: Fri, 5 Aug 2022 12:48:26 +0200 Subject: [PATCH 23/23] sentry - routes - fix intra link --- sentry/src/routes.rs | 8 ++++---- sentry/src/routes/analytics.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sentry/src/routes.rs b/sentry/src/routes.rs index 63ca7ee9c..617ff456d 100644 --- a/sentry/src/routes.rs +++ b/sentry/src/routes.rs @@ -295,7 +295,7 @@ //! //! Allowed keys: [`AllowedKey::Country`][primitives::analytics::query::AllowedKey::Country], [`AllowedKey::AdSlotType`][primitives::analytics::query::AllowedKey::AdSlotType] //! -//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery] +//! Request query parameters: [`AnalyticsQuery`] //! //! Response: [`AnalyticsResponse`] //! @@ -321,7 +321,7 @@ //! //! The route is handled by [`get_analytics()`]. //! -//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery] +//! Request query parameters: [`AnalyticsQuery`] //! //! Response: [`AnalyticsResponse`] //! @@ -337,7 +337,7 @@ //! //! The route is handled by [`get_analytics()`]. //! -//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery] +//! Request query parameters: [`AnalyticsQuery`] //! //! Response: [`AnalyticsResponse`] //! @@ -355,7 +355,7 @@ //! //! The route is handled by [`get_analytics()`]. //! -//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery] +//! Request query parameters: [`AnalyticsQuery`] //! //! Response: [`AnalyticsResponse`] //! diff --git a/sentry/src/routes/analytics.rs b/sentry/src/routes/analytics.rs index ac0d98036..f04968ec5 100644 --- a/sentry/src/routes/analytics.rs +++ b/sentry/src/routes/analytics.rs @@ -19,9 +19,9 @@ use primitives::{ }; /// GET `/v5/analytics` routes -/// Request query parameters: [`primitives::analytics::AnalyticsQuery`]. +/// Request query parameters: [`AnalyticsQuery`]. /// -/// Response: [`primitives::sentry::AnalyticsResponse`] +/// Response: [`AnalyticsResponse`] /// /// Analytics routes: /// - GET `/v5/analytics`