Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .DS_Store
Binary file not shown.
17 changes: 7 additions & 10 deletions primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,29 @@ postgres = ["bytes", "tokio-postgres", "deadpool-postgres"]
test-util = []

[[example]]
name = "channel_list_query"
required-features = ["test-util"]
name = "analytics_query"

[[example]]
name = "campaign_list_query"
required-features = ["test-util"]
name = "analytics_response"

[[example]]
name = "campaign_list_response"
name = "campaign_list_query"
required-features = ["test-util"]

[[example]]
name = "create_campaign"
name = "campaign_list_response"
required-features = ["test-util"]

[[example]]
name = "modify_campaign"
name = "channel_list_query"
required-features = ["test-util"]

[[example]]
name = "analytics_query"
name = "create_campaign"
required-features = ["test-util"]

[[example]]
name = "analytics_response"
required-features = ["test-util"]
name = "modify_campaign"

[dependencies]
# (De)Serialization
Expand Down
12 changes: 10 additions & 2 deletions primitives/examples/analytics_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
12 changes: 8 additions & 4 deletions primitives/examples/create_campaign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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,
Expand All @@ -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");

Expand Down
1 change: 0 additions & 1 deletion primitives/examples/modify_campaign.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use primitives::{sentry::campaign_modify::ModifyCampaign, unified_num::FromWhole, UnifiedNum};
use serde_json::json;
use std::str::FromStr;

fn main() {
{
Expand Down
14 changes: 11 additions & 3 deletions primitives/src/sentry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,10 @@ pub struct FetchedAnalytics {
pub segment: Option<String>,
}

// 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")]
/// ```
Expand Down Expand Up @@ -651,6 +652,13 @@ pub struct ValidationErrorResponse {
pub validation: Vec<String>,
}

/// 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<Event>,
}

pub mod channel_list {
use crate::{ChainId, Channel, ValidatorId};
use serde::{Deserialize, Serialize};
Expand Down
Binary file removed sentry/.DS_Store
Binary file not shown.
61 changes: 46 additions & 15 deletions sentry/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@
//!
//! Request body (json): [`ValidatorMessagesCreateRequest`](primitives::sentry::ValidatorMessagesCreateRequest)
//!
//! Example:
//! ##### Examples:
//!
//! ```json
//! {
//! "messages": [
Expand Down Expand Up @@ -231,14 +232,20 @@
//! **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()`].
//!
//! Request body (json): [`CreateCampaign`][primitives::sentry::campaign_create::CreateCampaign]
//!
//! 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).
Expand All @@ -251,22 +258,20 @@
//!
//! 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`].
//! Applies [`Campaign.event_submission`] rules and additional validation using [`check_access()`].
//!
//! 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`]
//!
Expand All @@ -290,10 +295,24 @@
//!
//! 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`]
//!
//! ##### 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**.
Expand All @@ -302,10 +321,14 @@
//!
//! The route is handled by [`get_analytics()`].
//!
//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery]
//! Request query parameters: [`AnalyticsQuery`]
//!
//! 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**.
Expand All @@ -314,10 +337,14 @@
//!
//! The route is handled by [`get_analytics()`].
//!
//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery]
//! Request query parameters: [`AnalyticsQuery`]
//!
//! 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.
Expand All @@ -328,10 +355,14 @@
//!
//! The route is handled by [`get_analytics()`].
//!
//! Request query parameters: [`AnalyticsQuery`][primitives::analytics::AnalyticsQuery]
//! Request query parameters: [`AnalyticsQuery`]
//!
//! Response: [`AnalyticsResponse`]
//!
//! ##### Examples
//!
//! See [GET `/v5/analytics`](#get-v5analytics)
//!
//! [`Adapter`]: adapter::Adapter
//! [`Address`]: primitives::Address
//! [`AllowedKey`]: primitives::analytics::query::AllowedKey
Expand Down
4 changes: 2 additions & 2 deletions sentry/src/routes/analytics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
32 changes: 10 additions & 22 deletions sentry/src/routes/campaign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ pub async fn fetch_campaign_ids_for_channel(

/// POST `/v5/campaign`
///
/// Request body (json): [`CreateCampaign`](`primitives::sentry::campaign_create::CreateCampaign`)
/// Request body (json): [`CreateCampaign`](primitives::sentry::campaign_create::CreateCampaign)
///
/// Response: [Campaign](`primitives::Campaign`)
/// Response: [`Campaign`](primitives::Campaign)
pub async fn create_campaign<C>(
req: Request<Body>,
app: &Application<C>,
Expand Down Expand Up @@ -593,8 +593,6 @@ pub mod update_campaign {

pub mod insert_events {

use std::collections::HashMap;

use crate::{
access::{self, check_access},
analytics,
Expand All @@ -608,7 +606,7 @@ pub mod insert_events {
use hyper::{Body, Request, Response};
use primitives::{
balances::{Balances, CheckedState, OverflowError},
sentry::{Event, SuccessResponse},
sentry::{Event, InsertEventsRequest, SuccessResponse},
Address, Campaign, CampaignId, ChainOf, DomainError, UnifiedNum, ValidatorDesc,
};
use slog::{error, Logger};
Expand Down Expand Up @@ -642,15 +640,9 @@ pub mod insert_events {

/// POST `/v5/campaign/:id/events`
///
/// Request body (json):
///
/// ```json
/// {
/// "events": [[`Event`](`primitives::Event`)]
/// }
/// ```
/// Request body (json): [`InsertEventsRequest`]
///
/// Response: [`SuccessResponse`](primitives::sentry::SuccessResponse)
/// Response: [`SuccessResponse`]
pub async fn handle_route<C: Locked + 'static>(
req: Request<Body>,
app: &Application<C>,
Expand All @@ -669,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::<HashMap<String, Vec<Event>>>(&body_bytes)?;

let events = request_body
.remove("events")
.ok_or_else(|| ResponseError::BadRequest("invalid request".to_string()))?;
let request_body = serde_json::from_slice::<InsertEventsRequest>(&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())
}

Expand All @@ -689,7 +677,7 @@ pub mod insert_events {
session: &Session,
campaign_context: &ChainOf<Campaign>,
events: Vec<Event>,
) -> Result<bool, ResponseError> {
) -> Result<(), ResponseError> {
let campaign = &campaign_context.context;

// handle events - check access
Expand Down Expand Up @@ -738,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.
Expand Down
9 changes: 5 additions & 4 deletions test_harness/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ mod tests {
balances::CheckedState,
sentry::{
campaign_create::CreateCampaign, AccountingResponse, AnalyticsResponse, DateHour,
Event, EventType, FetchedAnalytics, FetchedMetric, SuccessResponse, CLICK, IMPRESSION,
Event, EventType, FetchedAnalytics, FetchedMetric, InsertEventsRequest,
SuccessResponse, CLICK, IMPRESSION,
},
spender::Spender,
test_util::{
Expand Down Expand Up @@ -2692,9 +2693,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::<HashMap<_, _>>();
let request_body = InsertEventsRequest {
events: events.to_vec(),
};

let auth_token = sentry
.adapter
Expand Down