diff --git a/primitives/src/sentry.rs b/primitives/src/sentry.rs index 273d734aa..f799c6dca 100644 --- a/primitives/src/sentry.rs +++ b/primitives/src/sentry.rs @@ -312,7 +312,7 @@ pub struct FetchedAnalytics { pub segment: Option, } -/// The value of the requested analytics [`Metric`]. +/// The value of the requested analytics [`crate::analytics::Metric`]. #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] #[serde(untagged)] pub enum FetchedMetric { diff --git a/sentry/README.md b/sentry/README.md index f34a069c1..ff7296dc1 100644 --- a/sentry/README.md +++ b/sentry/README.md @@ -1,5 +1,15 @@ # Sentry +## REST API documentation for AdEx Protocol V5 + +For full details see [AIP #61](https://github.com/AmbireTech/aips/issues/61) and the tracking issue for this implementation https://github.com/AmbireTech/adex-validator-stack-rust/issues/377. + +REST API documentation can be generated using `rustdoc`: + +`cargo doc --all-features --open --lib` + +and checking the `sentry::routes` module. + ## Development ### Migrations diff --git a/sentry/src/analytics.rs b/sentry/src/analytics.rs index 5b03f2df7..2a136fa88 100644 --- a/sentry/src/analytics.rs +++ b/sentry/src/analytics.rs @@ -63,7 +63,6 @@ pub async fn record( (None, None) => None, }; - // TODO: tidy up this operation batch_update .entry(event) .and_modify(|analytics| { diff --git a/sentry/src/analytics_recorder.rs b/sentry/src/analytics_recorder.rs deleted file mode 100644 index ad9876373..000000000 --- a/sentry/src/analytics_recorder.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::epoch; -use crate::Session; -use primitives::sentry::Event; -use primitives::sentry::{ChannelReport, PublisherReport}; -use primitives::{BigNum, Channel, ValidatorId}; -use redis::aio::MultiplexedConnection; -use redis::pipe; -use slog::{error, Logger}; - -// Records only payout events -pub async fn record( - mut conn: MultiplexedConnection, - channel: Channel, - session: Session, - events: Vec<(Event, Option<(ValidatorId, BigNum)>)>, - logger: Logger, -) { - let mut db = pipe(); - - events - .iter() - .filter(|(ev, _)| ev.is_click_event() || ev.is_impression_event()) - .for_each(|(event, payout)| match event { - Event::Impression { - publisher, - ad_unit, - ad_slot, - referrer, - } - | Event::Click { - publisher, - ad_unit, - ad_slot, - referrer, - } => { - let divisor = BigNum::from(10u64.pow(18)); - let pay_amount = match payout { - Some((_, payout)) => payout - .div_floor(&divisor) - .to_f64() - .expect("Should always have a payout in f64 after division"), - // This should never happen, as the conditions we are checking for in the .filter are the same as getPayout's - None => return, - }; - - if let Some(ad_unit) = ad_unit { - db.zincr( - format!("{}:{}:{}", PublisherReport::AdUnit, event, publisher), - ad_unit.to_string(), - 1, - ) - .ignore(); - db.zincr( - format!("{}:{}:{}", ChannelReport::AdUnit, event, publisher), - ad_unit.to_string(), - 1, - ) - .ignore(); - } - - if let Some(ad_slot) = ad_slot { - db.zincr( - format!("{}:{}:{}", PublisherReport::AdSlot, event, publisher), - ad_slot.to_string(), - 1, - ) - .ignore(); - db.zincr( - format!("{}:{}:{}", PublisherReport::AdSlotPay, event, publisher), - ad_slot.to_string(), - pay_amount, - ) - .ignore(); - } - - if let Some(country) = &session.country { - db.zincr( - format!( - "{}:{}:{}:{}", - PublisherReport::Country, - epoch().floor(), - event, - publisher - ), - country, - 1, - ) - .ignore(); - } - - let hostname = (referrer.as_ref()) - .or_else(|| session.referrer_header.as_ref()) - .map(|rf| rf.split('/').nth(2).map(ToString::to_string)) - .flatten(); - - if let Some(hostname) = &hostname { - db.zincr( - format!("{}:{}:{}", PublisherReport::Hostname, event, publisher), - hostname, - 1, - ) - .ignore(); - db.zincr( - format!("{}:{}:{}", ChannelReport::Hostname, event, channel.id()), - hostname, - 1, - ) - .ignore(); - db.zincr( - format!("{}:{}:{}", ChannelReport::HostnamePay, event, channel.id()), - hostname, - 1, - ) - .ignore(); - } - } - }); - - if let Err(err) = db.query_async::<_, Option>(&mut conn).await { - error!(&logger, "Redis Database error: {}", err; "module" => "analytics-recorder"); - } -} diff --git a/sentry/src/lib.rs b/sentry/src/lib.rs index b4af3f0bd..ff245a7ca 100644 --- a/sentry/src/lib.rs +++ b/sentry/src/lib.rs @@ -27,29 +27,22 @@ use { routes::{ campaign, campaign::{campaign_list, create_campaign, update_campaign}, - cfg::config, + get_cfg, + get_analytics, channel::{ - add_spender_leaf, channel_list, create_validator_messages, get_accounting_for_channel, - get_all_spender_limits, get_spender_limits, last_approved, + add_spender_leaf, channel_list, get_accounting_for_channel, get_all_spender_limits, + get_spender_limits, last_approved, + validator_message::{ + create_validator_messages, extract_params, list_validator_messages, + }, }, - event_aggregate::list_channel_event_aggregates, - validator_message::{extract_params, list_validator_messages}, }, }; pub mod analytics; pub mod middleware; -pub mod routes { - pub mod analytics; - pub mod campaign; - pub mod cfg; - pub mod channel; - pub mod event_aggregate; - pub mod validator_message; -} - +pub mod routes; pub mod access; -pub mod analytics_recorder; pub mod application; pub mod db; pub mod payout; @@ -59,15 +52,12 @@ static LAST_APPROVED_BY_CHANNEL_ID: Lazy = Lazy::new(|| { Regex::new(r"^/v5/channel/0x([a-zA-Z0-9]{64})/last-approved/?$") .expect("The regex should be valid") }); -// Only the initial Regex to be matched. + +/// Only the initial Regex to be matched. static CHANNEL_VALIDATOR_MESSAGES: Lazy = Lazy::new(|| { Regex::new(r"^/v5/channel/0x([a-zA-Z0-9]{64})/validator-messages(/.*)?$") .expect("The regex should be valid") }); -static CHANNEL_EVENTS_AGGREGATES: Lazy = Lazy::new(|| { - Regex::new(r"^/v5/channel/0x([a-zA-Z0-9]{64})/events-aggregates/?$") - .expect("The regex should be valid") -}); static CHANNEL_SPENDER_LEAF_AND_TOTAL_DEPOSITED: Lazy = Lazy::new(|| { Regex::new(r"^/v5/channel/0x([a-zA-Z0-9]{64})/spender/0x([a-zA-Z0-9]{40})/?$") .expect("This regex should be valid") @@ -91,6 +81,8 @@ static CHANNEL_ACCOUNTING: Lazy = Lazy::new(|| { .expect("The regex should be valid") }); +/// Regex extracted parameters. +/// This struct is created manually on each of the matched routes. #[derive(Debug, Clone)] pub struct RouteParams(pub Vec); @@ -104,8 +96,7 @@ impl RouteParams { } } -// #[derive(Clone)] -// pub struct Application { +/// The Sentry REST web application pub struct Application { /// For sentry to work properly, we need an [`adapter::Adapter`] in a [`adapter::LockedState`] state. pub adapter: Adapter, @@ -152,7 +143,7 @@ where }; let mut response = match (req.uri().path(), req.method()) { - ("/cfg", &Method::GET) => config(req, self).await, + ("/cfg", &Method::GET) => get_cfg(req, self).await, ("/channel/list", &Method::GET) => channel_list(req, self).await, (route, _) if route.starts_with("/analytics") => analytics_router(req, self).await, // This is important because it prevents us from doing @@ -226,7 +217,6 @@ async fn analytics_router( mut req: Request, app: &Application, ) -> Result, ResponseError> { - use routes::analytics::analytics; let (route, method) = (req.uri().path(), req.method()); @@ -235,7 +225,7 @@ async fn analytics_router( let allowed_keys_for_request = vec![AllowedKey::Country, AllowedKey::AdSlotType] .into_iter() .collect(); - analytics(req, app, Some(allowed_keys_for_request), None).await + get_analytics(req, app, Some(allowed_keys_for_request), None).await } ("/analytics/for-advertiser", &Method::GET) => { let req = AuthRequired.call(req, app).await?; @@ -246,7 +236,7 @@ async fn analytics_router( .map(|auth| AuthenticateAs::Advertiser(auth.uid)) .ok_or(ResponseError::Unauthorized)?; - analytics(req, app, None, Some(authenticate_as)).await + get_analytics(req, app, None, Some(authenticate_as)).await } ("/analytics/for-publisher", &Method::GET) => { let authenticate_as = req @@ -256,7 +246,7 @@ async fn analytics_router( .ok_or(ResponseError::Unauthorized)?; let req = AuthRequired.call(req, app).await?; - analytics(req, app, None, Some(authenticate_as)).await + get_analytics(req, app, None, Some(authenticate_as)).await } ("/analytics/for-admin", &Method::GET) => { req = Chain::new() @@ -264,7 +254,7 @@ async fn analytics_router( .chain(IsAdmin) .apply(req, app) .await?; - analytics(req, app, None, None).await + get_analytics(req, app, None, None).await } _ => Err(ResponseError::NotFound), } @@ -328,20 +318,6 @@ async fn channels_router( .await?; create_validator_messages(req, app).await - } else if let (Some(caps), &Method::GET) = (CHANNEL_EVENTS_AGGREGATES.captures(&path), method) { - req = AuthRequired.call(req, app).await?; - - let param = RouteParams(vec![ - caps.get(1) - .map_or("".to_string(), |m| m.as_str().to_string()), - caps.get(2) - .map_or("".to_string(), |m| m.as_str().trim_matches('/').to_string()), - ]); - req.extensions_mut().insert(param); - - req = ChannelLoad.call(req, app).await?; - - list_channel_event_aggregates(req, app).await } else if let (Some(caps), &Method::GET) = ( CHANNEL_SPENDER_LEAF_AND_TOTAL_DEPOSITED.captures(&path), method, @@ -512,7 +488,7 @@ pub fn epoch() -> f64 { Utc::now().timestamp() as f64 / 2_628_000_000.0 } -// @TODO: Make pub(crate) +/// Sentry [`Application`] Session #[derive(Debug, Clone)] pub struct Session { pub ip: Option, @@ -521,6 +497,7 @@ pub struct Session { pub os: Option, } +/// Sentry [`Application`] Auth (Authentication) #[derive(Debug, Clone)] pub struct Auth { pub era: i64, diff --git a/sentry/src/routes.rs b/sentry/src/routes.rs new file mode 100644 index 000000000..086f7d2be --- /dev/null +++ b/sentry/src/routes.rs @@ -0,0 +1,131 @@ +//! Sentry REST API documentation +//! +//! ## Channel +//! +//! All routes are implemented under module [channel]. +//! +//! - [`GET /v5/channel/list`](crate::routes::channel::channel_list) +//! +//! todo +//! +//! - [`GET /v5/channel/:id/accounting`](channel::get_accounting_for_channel) +//! +//! todo +//! +//! - [`GET /v5/channel/:id/spender/:addr`](channel::get_spender_limits) (auth required) +//! +//! todo +//! +//! - [`POST /v5/channel/:id/spender/:addr`](channel::add_spender_leaf) (auth required) +//! +//! todo +//! +//! - [`GET /v5/channel/:id/spender/all`](channel::get_all_spender_limits) (auth required) +//! +//! todo +//! +//! - [`GET /v5/channel/:id/validator-messages`](channel::validator_message::list_validator_messages) +//! +//! - `GET /v5/channel/:id/validator-messages/:ValidatorId` - filter by ValidatorId +//! - `GET /v5/channel/:id/validator-messages/:ValidatorId/NewState+ApproveState` - filters by a given [`primitives::ValidatorId`] and a +//! [`Validator message types`](primitives::validator::MessageTypes). +//! +//! Request query parameters: [channel::validator_message::ValidatorMessagesListQuery] +//! Response: [primitives::sentry::ValidatorMessageResponse] +//! +//! - [`POST /v5/channel/:id/validator-messages`](channel::validator_message::create_validator_messages) (auth required) +//! +//! todo +//! +//! - [`POST /v5/channel/:id/last-approved`](channel::last_approved) +//! +//! todo +//! +//! - `POST /v5/channel/:id/pay` (auth required) +//! +//! TODO: implement and document as part of issue #382 +//! +//! Channel Payout with authentication of the spender +//! +//! Withdrawals of advertiser funds - re-introduces the PAY event with a separate route. +//! +//! - `GET /v5/channel/:id/get-leaf` +//! +//! TODO: implement and document as part of issue #382 +//! +//! This route gets the latest approved state (`NewState`/`ApproveState` pair), +//! and finds the given `spender`/`earner` in the balances tree, and produce a merkle proof for it. +//! This is useful for the Platform to verify if a spender leaf really exists. +//! +//! Query parameters: +//! +//! - `spender=[0x...]` or `earner=[0x...]` (required) +//! +//! Example Spender: +//! +//! `/get-leaf?spender=0x...` +//! +//! Example Earner: +//! +//! `/get-leaf?earner=0x....` +//! This module includes all routes for `Sentry` and the documentation of each Request/Response. +//! +//! ## Campaign +//! +//! All routes are implemented under module [campaign]. +//! +//! - `GET /v5/campaign/list` +//! +//! Lists all campaigns with pagination and orders them in descending order (`DESC`) by `Campaign.created`. This ensures that the order in the pages will not change if a new `Campaign` is created while still retrieving a page. +//! +//! Query parameters: +//! - `page=[integer]` (optional) default: `0` +//! - `creator=[0x....]` (optional) - address of the creator to be filtered by +//! - `activeTo=[integer]` (optional) in seconds - filters campaigns by `Campaign.active.to > query.activeTo` +//! - `validator=[0x...]` or `leader=[0x...]` (optional) - address of the validator to be filtered by. You can either +//! - `validator=[0x...]` - it will return all `Campaign`s where this address is **either** `Channel.leader` or `Channel.follower` +//! - `leader=[0x...]` - it will return all `Campaign`s where this address is `Channel.leader` +//! +//! +//! - `POST /v5/campaign` (auth required) +//! +//! Create a new Campaign. +//! +//! It will make sure the `Channel` is created if new and it will update the spendable amount using the `Adapter::get_deposit()`. +//! +//! Authentication: **required** to validate `Campaign.creator == Auth.uid` +//! +//! Request Body: [`primitives::sentry::campaign_create::CreateCampaign`] (json) +//! +//! `POST /v5/campaign/:id/close` (auth required) +//! +//! todo +//! +//! ## Analytics +//! +//! - `GET /v5/analytics` +//! +//! todo +//! +//! - `GET /v5/analytics/for-publisher` (auth required) +//! +//! todo +//! +//! - `GET /v5/analytics/for-advertiser` (auth required) +//! +//! todo +//! +//! - `GET /v5/analytics/for-admin` (auth required) +//! +//! todo +//! +pub use analytics::analytics as get_analytics; + +pub use cfg::config as get_cfg; + +// `analytics` module has single request, so we only export this request +mod analytics; +pub mod campaign; +// `cfg` module has single request, so we only export this request +mod cfg; +pub mod channel; \ No newline at end of file diff --git a/sentry/src/routes/analytics.rs b/sentry/src/routes/analytics.rs index 4e3cc6d53..091664050 100644 --- a/sentry/src/routes/analytics.rs +++ b/sentry/src/routes/analytics.rs @@ -1,3 +1,6 @@ +//! `/v5/analytics` routes +//! + use std::collections::HashSet; use crate::{db::analytics::get_analytics, success_response, Application, ResponseError}; @@ -8,6 +11,8 @@ use primitives::analytics::{ AnalyticsQuery, AuthenticateAs, ANALYTICS_QUERY_LIMIT, }; +/// `GET /analytics` request +/// with query parameters: [`primitives::analytics::AnalyticsQuery`]. pub async fn analytics( req: Request, app: &Application, diff --git a/sentry/src/routes/campaign.rs b/sentry/src/routes/campaign.rs index 8806e9b0d..3f8c82f24 100644 --- a/sentry/src/routes/campaign.rs +++ b/sentry/src/routes/campaign.rs @@ -1,3 +1,4 @@ +//! `/v5/campaign` routes use crate::{ db::{ accounting::{get_accounting, Side}, diff --git a/sentry/src/routes/cfg.rs b/sentry/src/routes/cfg.rs index 853a7710b..fce1d4a38 100644 --- a/sentry/src/routes/cfg.rs +++ b/sentry/src/routes/cfg.rs @@ -1,9 +1,12 @@ +//! `GET /cfg` request + use crate::Application; use crate::ResponseError; use adapter::client::Locked; use hyper::header::CONTENT_TYPE; use hyper::{Body, Request, Response}; +/// `GET /cfg` request pub async fn config( _: Request, app: &Application, diff --git a/sentry/src/routes/channel.rs b/sentry/src/routes/channel.rs index 129d6f154..c97d93143 100644 --- a/sentry/src/routes/channel.rs +++ b/sentry/src/routes/channel.rs @@ -1,11 +1,14 @@ +//! Channel - `/v5/channel` routes +//! + use crate::db::{ accounting::{get_all_accountings_for_channel, update_accounting, Side}, event_aggregate::{latest_approve_state_v5, latest_heartbeats, latest_new_state_v5}, - insert_channel, insert_validator_messages, list_channels, + insert_channel, list_channels, spendable::{fetch_spendable, get_all_spendables_for_channel, update_spendable}, DbPool, }; -use crate::{success_response, Application, Auth, ResponseError, RouteParams}; +use crate::{success_response, Application, ResponseError, RouteParams}; use adapter::{client::Locked, Adapter}; use futures::future::try_join_all; use hyper::{Body, Request, Response}; @@ -17,7 +20,7 @@ use primitives::{ LastApproved, LastApprovedQuery, LastApprovedResponse, SpenderResponse, SuccessResponse, }, spender::{Spendable, Spender}, - validator::{MessageTypes, NewState}, + validator::NewState, Address, Channel, Deposit, UnifiedNum, }; use slog::{error, Logger}; @@ -107,45 +110,6 @@ pub async fn last_approved( .unwrap()) } -pub async fn create_validator_messages( - req: Request, - app: &Application, -) -> Result, ResponseError> { - let session = req - .extensions() - .get::() - .expect("auth request session") - .to_owned(); - - let channel = req - .extensions() - .get::() - .expect("Request should have Channel") - .to_owned(); - - let into_body = req.into_body(); - let body = hyper::body::to_bytes(into_body).await?; - - let request_body = serde_json::from_slice::>>(&body)?; - let messages = request_body - .get("messages") - .ok_or_else(|| ResponseError::BadRequest("missing messages body".to_string()))?; - - match channel.find_validator(session.uid) { - None => Err(ResponseError::Unauthorized), - _ => { - try_join_all(messages.iter().map(|message| { - insert_validator_messages(&app.pool, &channel, &session.uid, message) - })) - .await?; - - Ok(success_response(serde_json::to_string(&SuccessResponse { - success: true, - })?)) - } - } -} - /// This will make sure to insert/get the `Channel` from DB before attempting to create the `Spendable` async fn create_or_update_spendable_document( adapter: &Adapter, @@ -373,6 +337,9 @@ async fn get_corresponding_new_state( new_state } +/// `GET /v5/channel/0xXXX.../accounting` request +/// +/// Response: [`AccountingResponse::`] pub async fn get_accounting_for_channel( req: Request, app: &Application, @@ -412,6 +379,146 @@ pub async fn get_accounting_for_channel( Ok(success_response(serde_json::to_string(&res)?)) } +/// [`Channel`] [validator messages](primitives::validator::MessageTypes) routes +/// starting with `/v5/channel/0xXXX.../validator-messages` +/// +pub mod validator_message { + use std::collections::HashMap; + + use crate::{ + db::{get_validator_messages, insert_validator_messages}, + Auth, + }; + use crate::{success_response, Application, ResponseError}; + use adapter::client::Locked; + use futures::future::try_join_all; + use hyper::{Body, Request, Response}; + use primitives::{ + sentry::{SuccessResponse, ValidatorMessageResponse}, + validator::MessageTypes, + }; + use primitives::{Channel, DomainError, ValidatorId}; + use serde::Deserialize; + + #[derive(Deserialize)] + pub struct ValidatorMessagesListQuery { + limit: Option, + } + + pub fn extract_params( + from_path: &str, + ) -> Result<(Option, Vec), DomainError> { + // trim the `/` at the beginning & end if there is one or more + // and split the rest of the string at the `/` + let split: Vec<&str> = from_path.trim_matches('/').split('/').collect(); + + if split.len() > 2 { + return Err(DomainError::InvalidArgument( + "Too many parameters".to_string(), + )); + } + + let validator_id = split + .get(0) + // filter an empty string + .filter(|string| !string.is_empty()) + // then try to map it to ValidatorId + .map(|string| string.parse()) + // Transpose in order to check for an error from the conversion + .transpose()?; + + let message_types = split + .get(1) + .filter(|string| !string.is_empty()) + .map(|string| string.split('+').map(|s| s.to_string()).collect()); + + Ok((validator_id, message_types.unwrap_or_default())) + } + + /// `GET /v5/channel/0xXXX.../validator-messages` + /// with query parameters: [`ValidatorMessagesListQuery`]. + pub async fn list_validator_messages( + req: Request, + app: &Application, + validator_id: &Option, + message_types: &[String], + ) -> Result, ResponseError> { + let query = serde_urlencoded::from_str::( + req.uri().query().unwrap_or(""), + )?; + + let channel = req + .extensions() + .get::() + .expect("Request should have Channel"); + + let config_limit = app.config.msgs_find_limit as u64; + let limit = query + .limit + .filter(|n| *n >= 1) + .unwrap_or(config_limit) + .min(config_limit); + + let validator_messages = + get_validator_messages(&app.pool, &channel.id(), validator_id, message_types, limit) + .await?; + + let response = ValidatorMessageResponse { validator_messages }; + + Ok(success_response(serde_json::to_string(&response)?)) + } + + /// `POST /v5/channel/0xXXX.../validator-messages` with Request body (json): + /// ```json + /// { + /// "messages": [ + /// /// validator messages + /// ... + /// ] + /// } + /// ``` + /// + /// Validator messages: [`MessageTypes`] + pub async fn create_validator_messages( + req: Request, + app: &Application, + ) -> Result, ResponseError> { + let session = req + .extensions() + .get::() + .expect("auth request session") + .to_owned(); + + let channel = req + .extensions() + .get::() + .expect("Request should have Channel") + .to_owned(); + + let into_body = req.into_body(); + let body = hyper::body::to_bytes(into_body).await?; + + let request_body = serde_json::from_slice::>>(&body)?; + let messages = request_body + .get("messages") + .ok_or_else(|| ResponseError::BadRequest("missing messages body".to_string()))?; + + match channel.find_validator(session.uid) { + None => Err(ResponseError::Unauthorized), + _ => { + try_join_all(messages.iter().map(|message| { + insert_validator_messages(&app.pool, &channel, &session.uid, message) + })) + .await?; + + Ok(success_response(serde_json::to_string(&SuccessResponse { + success: true, + })?)) + } + } + } +} + #[cfg(test)] mod test { use super::*; diff --git a/sentry/src/routes/event_aggregate.rs b/sentry/src/routes/event_aggregate.rs deleted file mode 100644 index 92ff5abdf..000000000 --- a/sentry/src/routes/event_aggregate.rs +++ /dev/null @@ -1,52 +0,0 @@ -use adapter::client::Locked; -use chrono::{serde::ts_milliseconds_option, DateTime, Utc}; -use hyper::{Body, Request, Response}; -use serde::Deserialize; - -use primitives::{sentry::EventAggregateResponse, Channel}; - -use crate::{success_response, Application, Auth, ResponseError}; - -#[derive(Deserialize)] -pub struct EventAggregatesQuery { - #[serde(default, with = "ts_milliseconds_option")] - #[allow(dead_code)] - after: Option>, -} -#[deprecated = "V5 - Double check what is need from the event aggregates from V4"] -pub async fn list_channel_event_aggregates( - req: Request, - _app: &Application, -) -> Result, ResponseError> { - let channel = *req - .extensions() - .get::() - .expect("Request should have Channel"); - - let auth = req - .extensions() - .get::() - .ok_or(ResponseError::Unauthorized)?; - - let _query = - serde_urlencoded::from_str::(req.uri().query().unwrap_or(""))?; - - let _from = channel.find_validator(auth.uid); - - let event_aggregates = vec![]; - // let event_aggregates = list_event_aggregates( - // &app.pool, - // &channel.id, - // app.config.events_find_limit, - // &from, - // &query.after, - // ) - // .await?; - - let response = EventAggregateResponse { - channel, - events: event_aggregates, - }; - - Ok(success_response(serde_json::to_string(&response)?)) -} diff --git a/sentry/src/routes/validator_message.rs b/sentry/src/routes/validator_message.rs deleted file mode 100644 index 97632c259..000000000 --- a/sentry/src/routes/validator_message.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::db::get_validator_messages; -use crate::{success_response, Application, ResponseError}; -use adapter::client::Locked; -use hyper::{Body, Request, Response}; -use primitives::sentry::ValidatorMessageResponse; -use primitives::{Channel, DomainError, ValidatorId}; -use serde::Deserialize; - -#[derive(Deserialize)] -pub struct ValidatorMessagesListQuery { - limit: Option, -} - -pub fn extract_params(from_path: &str) -> Result<(Option, Vec), DomainError> { - // trim the `/` at the beginning & end if there is one or more - // and split the rest of the string at the `/` - let split: Vec<&str> = from_path.trim_matches('/').split('/').collect(); - - if split.len() > 2 { - return Err(DomainError::InvalidArgument( - "Too many parameters".to_string(), - )); - } - - let validator_id = split - .get(0) - // filter an empty string - .filter(|string| !string.is_empty()) - // then try to map it to ValidatorId - .map(|string| string.parse()) - // Transpose in order to check for an error from the conversion - .transpose()?; - - let message_types = split - .get(1) - .filter(|string| !string.is_empty()) - .map(|string| string.split('+').map(|s| s.to_string()).collect()); - - Ok((validator_id, message_types.unwrap_or_default())) -} - -pub async fn list_validator_messages( - req: Request, - app: &Application, - validator_id: &Option, - message_types: &[String], -) -> Result, ResponseError> { - let query = - serde_urlencoded::from_str::(req.uri().query().unwrap_or(""))?; - - let channel = req - .extensions() - .get::() - .expect("Request should have Channel"); - - let config_limit = app.config.msgs_find_limit as u64; - let limit = query - .limit - .filter(|n| *n >= 1) - .unwrap_or(config_limit) - .min(config_limit); - - let validator_messages = - get_validator_messages(&app.pool, &channel.id(), validator_id, message_types, limit) - .await?; - - let response = ValidatorMessageResponse { validator_messages }; - - Ok(success_response(serde_json::to_string(&response)?)) -}