Skip to content

Commit b6005d8

Browse files
authored
Merge pull request #513 from AmbireTech/sentry-lib-and-routing-organization
sentry - organize modules
2 parents e616efd + 5b61d4d commit b6005d8

File tree

16 files changed

+637
-611
lines changed

16 files changed

+637
-611
lines changed

sentry/src/application.rs

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,64 @@ use hyper::{
66
Error, Server,
77
};
88
use once_cell::sync::Lazy;
9-
use primitives::config::Environment;
9+
use primitives::{config::Environment, ValidatorId};
1010
use redis::ConnectionInfo;
1111
use serde::{Deserialize, Deserializer};
1212
use slog::{error, info};
1313

14+
use crate::{
15+
db::{CampaignRemaining, DbPool},
16+
middleware::{
17+
auth::Authenticate,
18+
cors::{cors, Cors},
19+
Middleware,
20+
},
21+
platform::PlatformApi,
22+
response::{map_response_error, ResponseError},
23+
routes::{
24+
get_cfg,
25+
routers::{analytics_router, campaigns_router, channels_router},
26+
},
27+
};
28+
use adapter::Adapter;
29+
use hyper::{Body, Method, Request, Response};
30+
use redis::aio::MultiplexedConnection;
31+
use slog::Logger;
32+
1433
/// an error used when deserializing a [`Config`] instance from environment variables
1534
/// see [`Config::from_env()`]
1635
pub use envy::Error as EnvError;
1736

18-
use crate::Application;
19-
2037
pub const DEFAULT_PORT: u16 = 8005;
2138
pub const DEFAULT_IP_ADDR: IpAddr = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
2239
pub static DEFAULT_REDIS_URL: Lazy<ConnectionInfo> = Lazy::new(|| {
2340
"redis://127.0.0.1:6379"
2441
.parse::<ConnectionInfo>()
2542
.expect("Valid URL")
2643
});
44+
/// Sentry Application config set by environment variables
2745
#[derive(Debug, Deserialize, Clone)]
28-
pub struct Config {
46+
pub struct EnvConfig {
2947
/// Defaults to `Development`: [`Environment::default()`]
3048
pub env: Environment,
3149
/// The port on which the Sentry REST API will be accessible.
32-
#[serde(default = "default_port")]
50+
///
3351
/// Defaults to `8005`: [`DEFAULT_PORT`]
52+
#[serde(default = "default_port")]
3453
pub port: u16,
3554
/// The address on which the Sentry REST API will be accessible.
3655
/// `0.0.0.0` can be used for Docker.
3756
/// `127.0.0.1` can be used for locally running servers.
38-
#[serde(default = "default_ip_addr")]
57+
///
3958
/// Defaults to `0.0.0.0`: [`DEFAULT_IP_ADDR`]
59+
#[serde(default = "default_ip_addr")]
4060
pub ip_addr: IpAddr,
4161
#[serde(deserialize_with = "redis_url", default = "default_redis_url")]
4262
/// Defaults to locally running Redis server: [`DEFAULT_REDIS_URL`]
4363
pub redis_url: ConnectionInfo,
4464
}
4565

46-
impl Config {
66+
impl EnvConfig {
4767
/// Deserialize the application [`Config`] from Environment variables.
4868
pub fn from_env() -> Result<Self, EnvError> {
4969
envy::from_env()
@@ -69,6 +89,72 @@ fn default_redis_url() -> ConnectionInfo {
6989
DEFAULT_REDIS_URL.clone()
7090
}
7191

92+
/// The Sentry REST web application
93+
pub struct Application<C: Locked + 'static> {
94+
/// For sentry to work properly, we need an [`adapter::Adapter`] in a [`adapter::LockedState`] state.
95+
pub adapter: Adapter<C>,
96+
pub config: primitives::Config,
97+
pub logger: Logger,
98+
pub redis: MultiplexedConnection,
99+
pub pool: DbPool,
100+
pub campaign_remaining: CampaignRemaining,
101+
pub platform_api: PlatformApi,
102+
}
103+
104+
impl<C> Application<C>
105+
where
106+
C: Locked,
107+
{
108+
pub fn new(
109+
adapter: Adapter<C>,
110+
config: primitives::Config,
111+
logger: Logger,
112+
redis: MultiplexedConnection,
113+
pool: DbPool,
114+
campaign_remaining: CampaignRemaining,
115+
platform_api: PlatformApi,
116+
) -> Self {
117+
Self {
118+
adapter,
119+
config,
120+
logger,
121+
redis,
122+
pool,
123+
campaign_remaining,
124+
platform_api,
125+
}
126+
}
127+
128+
pub async fn handle_routing(&self, req: Request<Body>) -> Response<Body> {
129+
let headers = match cors(&req) {
130+
Some(Cors::Simple(headers)) => headers,
131+
// if we have a Preflight, just return the response directly
132+
Some(Cors::Preflight(response)) => return response,
133+
None => Default::default(),
134+
};
135+
136+
let req = match Authenticate.call(req, self).await {
137+
Ok(req) => req,
138+
Err(error) => return map_response_error(error),
139+
};
140+
141+
let mut response = match (req.uri().path(), req.method()) {
142+
("/cfg", &Method::GET) => get_cfg(req, self).await,
143+
(route, _) if route.starts_with("/v5/analytics") => analytics_router(req, self).await,
144+
// This is important because it prevents us from doing
145+
// expensive regex matching for routes without /channel
146+
(path, _) if path.starts_with("/v5/channel") => channels_router(req, self).await,
147+
(path, _) if path.starts_with("/v5/campaign") => campaigns_router(req, self).await,
148+
_ => Err(ResponseError::NotFound),
149+
}
150+
.unwrap_or_else(map_response_error);
151+
152+
// extend the headers with the initial headers we have from CORS (if there are some)
153+
response.headers_mut().extend(headers);
154+
response
155+
}
156+
}
157+
72158
impl<C: Locked + 'static> Application<C> {
73159
/// Starts the `hyper` `Server`.
74160
pub async fn run(self, socket_addr: SocketAddr) {
@@ -107,6 +193,24 @@ impl<C: Locked> Clone for Application<C> {
107193
}
108194
}
109195

196+
/// Sentry [`Application`] Session
197+
#[derive(Debug, Clone)]
198+
pub struct Session {
199+
pub ip: Option<String>,
200+
pub country: Option<String>,
201+
pub referrer_header: Option<String>,
202+
pub os: Option<String>,
203+
}
204+
205+
/// Validated Authentication for the Sentry [`Application`].
206+
#[derive(Debug, Clone)]
207+
pub struct Auth {
208+
pub era: i64,
209+
pub uid: ValidatorId,
210+
/// The Chain for which this authentication was validated
211+
pub chain: primitives::Chain,
212+
}
213+
110214
#[cfg(test)]
111215
mod test {
112216
use serde_json::json;

0 commit comments

Comments
 (0)