Loading src/handlers/exit_node.rs 0 → 100644 +55 −0 Original line number Diff line number Diff line use std::sync::{Arc, RwLock}; use actix_web::{error::ErrorInternalServerError, web, Error, HttpResponse}; use sqlx::PgPool; use crate::metrics; use crate::models::factory::{GenericInfo, ResponseFactory}; use crate::models::query::{domain::ParametersType, params::QueryFilters}; use crate::models::responses::exit_node::ExitNode; use crate::utils::get_truncated; pub async fn get_exit_node( params: QueryFilters, factory: web::Data<Arc<RwLock<ResponseFactory>>>, pg: web::Data<PgPool>, ) -> Result<HttpResponse, Error> { let lock = factory.read().unwrap(); let GenericInfo { total_bridges, total_relays, .. } = lock.generic_info; let mut response = match params.r#type { Some(ParametersType::Bridge) => lock.bridges_response(), Some(ParametersType::Relay) | None => lock.relays_response(), }; drop(lock); match params.r#type.unwrap_or(ParametersType::Relay) { ParametersType::Relay => { let relays = metrics::exit_addresses(&pg, ¶ms).await.map_err(ErrorInternalServerError)?; let relays = relays.into_iter().map(ExitNode::from).collect(); response.relays(relays); response.relays_skipped(params.offset.map(|f| f.into())); response.relays_truncated(get_truncated(total_relays, params.limit, params.offset)); } ParametersType::Bridge => { let bridges = metrics::exit_addresses(&pg, ¶ms).await.map_err(ErrorInternalServerError)?; let bridges = bridges.into_iter().map(ExitNode::from).collect(); response.bridges(bridges); response.bridges_skipped(params.offset.map(|f| f.into())); response.bridges_truncated(get_truncated(total_bridges, params.limit, params.offset)); } } let exit_node = response.build().map_err(ErrorInternalServerError)?; Ok(HttpResponse::Ok().json(exit_node)) } src/handlers/mod.rs +2 −0 Original line number Diff line number Diff line mod bandwidth; mod clients; mod details; mod exit_node; mod meta; mod summary; mod weights; Loading @@ -8,6 +9,7 @@ mod weights; pub use bandwidth::*; pub use clients::*; pub use details::*; pub use exit_node::*; pub use meta::*; pub use summary::*; pub use weights::*; src/metrics/exit_node.rs 0 → 100644 +119 −0 Original line number Diff line number Diff line use sea_query::{Expr, PostgresQueryBuilder, Query}; use sea_query_binder::SqlxBinder; use sqlx::{types::chrono::NaiveDateTime, PgPool}; use crate::models::query::{domain::ParametersType, params::QueryFilters}; use super::tables::{ExitNode, ServerStatus}; #[derive(Debug, Clone, sqlx::FromRow)] pub struct ExitNodeRow { pub nickname: String, pub fingerprint: String, pub exit_addresses: String, pub published: NaiveDateTime, } pub async fn exit_addresses( pg: &PgPool, filters: &QueryFilters, ) -> Result<Vec<ExitNodeRow>, String> { if filters.search.is_some() { return Err("search query params is not supported yet.".to_string()); } if filters.os.is_some() { return Err("os query params is not supported yet.".to_string()); } if filters.host_name.is_some() { return Err("host_name query params is not supported yet.".to_string()); } if filters.fields.is_some() { return Err("fields query params is not supported yet.".to_string()); } if filters.contact.is_some() { return Err("contact query params is not supported yet.".to_string()); } if filters.first_seen_days.is_some() { return Err("first_seen_days query params is not supported yet.".to_string()); } if filters.last_seen_days.is_some() { return Err("last_seen_days query params is not supported yet.".to_string()); } if filters.family.is_some() { return Err("family query params is not supported yet.".to_string()); } if filters.lookup.is_some() { return Err("lookup query params is not supported yet.".to_string()); } let mut q = Query::select() .columns([ (ServerStatus::Table, ServerStatus::Nickname), (ServerStatus::Table, ServerStatus::Fingerprint), ]) .columns([ (ExitNode::Table, ExitNode::ExitAddresses), (ExitNode::Table, ExitNode::Published), ]) .from(ServerStatus::Table) .left_join( ExitNode::Table, Expr::col((ExitNode::Table, ExitNode::Fingerprint)) .equals((ServerStatus::Table, ServerStatus::Fingerprint)), ) .to_owned(); let is_bridge = match filters.r#type.unwrap_or(ParametersType::Relay) { ParametersType::Relay => false, ParametersType::Bridge => true, }; q.and_where(Expr::col(ServerStatus::IsBridge).eq(is_bridge)); if let Some(ref r#as) = filters.r#as { q.and_where(Expr::col((ServerStatus::Table, ServerStatus::AutonomousSystem)).eq(r#as)); } if let Some(ref fs) = filters.first_seen_since { let timestamp_stmt = format!("to_timestamp('{}', 'YYYY-MM-DD')", fs); q.and_where(Expr::col((ServerStatus::Table, ServerStatus::FirstSeen)).gte(timestamp_stmt)); } if let Some(ref ls) = filters.last_seen_since { let timestamp_stmt = format!("to_timestamp('{}', 'YYYY-MM-DD')", ls); q.and_where(Expr::col((ServerStatus::Table, ServerStatus::LastSeen)).gte(timestamp_stmt)); } if let Some(running) = filters.running { q.and_where(Expr::col((ServerStatus::Table, ServerStatus::Running)).eq(running)); } if let Some(ref country) = filters.country { q.and_where(Expr::col((ServerStatus::Table, ServerStatus::Country)).eq(country.as_ref())); } if let Some(ref as_name) = filters.as_name { q.and_where(Expr::col((ServerStatus::Table, ServerStatus::AsName)).eq(as_name)); } let limit = match filters.limit { Some(limit) => limit.as_ref().to_owned() as u64, None => 100, }; q.limit(limit); let (sql, values) = q.build_sqlx(PostgresQueryBuilder); sqlx::query_as_with::<_, ExitNodeRow, _>(&sql, values) .fetch_all(pg) .await .map_err(|e| e.to_string()) } src/metrics/mod.rs +2 −0 Original line number Diff line number Diff line mod bandwidth; mod details; mod exit_node; mod summary; mod tables; pub use bandwidth::*; pub use details::*; pub use exit_node::*; pub use summary::*; src/metrics/tables.rs +8 −0 Original line number Diff line number Diff line Loading @@ -48,3 +48,11 @@ pub enum ServerDescriptor { Contact, Ipv6DefaultPolicy, } #[derive(Iden)] pub enum ExitNode { Table, Fingerprint, Published, ExitAddresses, } Loading
src/handlers/exit_node.rs 0 → 100644 +55 −0 Original line number Diff line number Diff line use std::sync::{Arc, RwLock}; use actix_web::{error::ErrorInternalServerError, web, Error, HttpResponse}; use sqlx::PgPool; use crate::metrics; use crate::models::factory::{GenericInfo, ResponseFactory}; use crate::models::query::{domain::ParametersType, params::QueryFilters}; use crate::models::responses::exit_node::ExitNode; use crate::utils::get_truncated; pub async fn get_exit_node( params: QueryFilters, factory: web::Data<Arc<RwLock<ResponseFactory>>>, pg: web::Data<PgPool>, ) -> Result<HttpResponse, Error> { let lock = factory.read().unwrap(); let GenericInfo { total_bridges, total_relays, .. } = lock.generic_info; let mut response = match params.r#type { Some(ParametersType::Bridge) => lock.bridges_response(), Some(ParametersType::Relay) | None => lock.relays_response(), }; drop(lock); match params.r#type.unwrap_or(ParametersType::Relay) { ParametersType::Relay => { let relays = metrics::exit_addresses(&pg, ¶ms).await.map_err(ErrorInternalServerError)?; let relays = relays.into_iter().map(ExitNode::from).collect(); response.relays(relays); response.relays_skipped(params.offset.map(|f| f.into())); response.relays_truncated(get_truncated(total_relays, params.limit, params.offset)); } ParametersType::Bridge => { let bridges = metrics::exit_addresses(&pg, ¶ms).await.map_err(ErrorInternalServerError)?; let bridges = bridges.into_iter().map(ExitNode::from).collect(); response.bridges(bridges); response.bridges_skipped(params.offset.map(|f| f.into())); response.bridges_truncated(get_truncated(total_bridges, params.limit, params.offset)); } } let exit_node = response.build().map_err(ErrorInternalServerError)?; Ok(HttpResponse::Ok().json(exit_node)) }
src/handlers/mod.rs +2 −0 Original line number Diff line number Diff line mod bandwidth; mod clients; mod details; mod exit_node; mod meta; mod summary; mod weights; Loading @@ -8,6 +9,7 @@ mod weights; pub use bandwidth::*; pub use clients::*; pub use details::*; pub use exit_node::*; pub use meta::*; pub use summary::*; pub use weights::*;
src/metrics/exit_node.rs 0 → 100644 +119 −0 Original line number Diff line number Diff line use sea_query::{Expr, PostgresQueryBuilder, Query}; use sea_query_binder::SqlxBinder; use sqlx::{types::chrono::NaiveDateTime, PgPool}; use crate::models::query::{domain::ParametersType, params::QueryFilters}; use super::tables::{ExitNode, ServerStatus}; #[derive(Debug, Clone, sqlx::FromRow)] pub struct ExitNodeRow { pub nickname: String, pub fingerprint: String, pub exit_addresses: String, pub published: NaiveDateTime, } pub async fn exit_addresses( pg: &PgPool, filters: &QueryFilters, ) -> Result<Vec<ExitNodeRow>, String> { if filters.search.is_some() { return Err("search query params is not supported yet.".to_string()); } if filters.os.is_some() { return Err("os query params is not supported yet.".to_string()); } if filters.host_name.is_some() { return Err("host_name query params is not supported yet.".to_string()); } if filters.fields.is_some() { return Err("fields query params is not supported yet.".to_string()); } if filters.contact.is_some() { return Err("contact query params is not supported yet.".to_string()); } if filters.first_seen_days.is_some() { return Err("first_seen_days query params is not supported yet.".to_string()); } if filters.last_seen_days.is_some() { return Err("last_seen_days query params is not supported yet.".to_string()); } if filters.family.is_some() { return Err("family query params is not supported yet.".to_string()); } if filters.lookup.is_some() { return Err("lookup query params is not supported yet.".to_string()); } let mut q = Query::select() .columns([ (ServerStatus::Table, ServerStatus::Nickname), (ServerStatus::Table, ServerStatus::Fingerprint), ]) .columns([ (ExitNode::Table, ExitNode::ExitAddresses), (ExitNode::Table, ExitNode::Published), ]) .from(ServerStatus::Table) .left_join( ExitNode::Table, Expr::col((ExitNode::Table, ExitNode::Fingerprint)) .equals((ServerStatus::Table, ServerStatus::Fingerprint)), ) .to_owned(); let is_bridge = match filters.r#type.unwrap_or(ParametersType::Relay) { ParametersType::Relay => false, ParametersType::Bridge => true, }; q.and_where(Expr::col(ServerStatus::IsBridge).eq(is_bridge)); if let Some(ref r#as) = filters.r#as { q.and_where(Expr::col((ServerStatus::Table, ServerStatus::AutonomousSystem)).eq(r#as)); } if let Some(ref fs) = filters.first_seen_since { let timestamp_stmt = format!("to_timestamp('{}', 'YYYY-MM-DD')", fs); q.and_where(Expr::col((ServerStatus::Table, ServerStatus::FirstSeen)).gte(timestamp_stmt)); } if let Some(ref ls) = filters.last_seen_since { let timestamp_stmt = format!("to_timestamp('{}', 'YYYY-MM-DD')", ls); q.and_where(Expr::col((ServerStatus::Table, ServerStatus::LastSeen)).gte(timestamp_stmt)); } if let Some(running) = filters.running { q.and_where(Expr::col((ServerStatus::Table, ServerStatus::Running)).eq(running)); } if let Some(ref country) = filters.country { q.and_where(Expr::col((ServerStatus::Table, ServerStatus::Country)).eq(country.as_ref())); } if let Some(ref as_name) = filters.as_name { q.and_where(Expr::col((ServerStatus::Table, ServerStatus::AsName)).eq(as_name)); } let limit = match filters.limit { Some(limit) => limit.as_ref().to_owned() as u64, None => 100, }; q.limit(limit); let (sql, values) = q.build_sqlx(PostgresQueryBuilder); sqlx::query_as_with::<_, ExitNodeRow, _>(&sql, values) .fetch_all(pg) .await .map_err(|e| e.to_string()) }
src/metrics/mod.rs +2 −0 Original line number Diff line number Diff line mod bandwidth; mod details; mod exit_node; mod summary; mod tables; pub use bandwidth::*; pub use details::*; pub use exit_node::*; pub use summary::*;
src/metrics/tables.rs +8 −0 Original line number Diff line number Diff line Loading @@ -48,3 +48,11 @@ pub enum ServerDescriptor { Contact, Ipv6DefaultPolicy, } #[derive(Iden)] pub enum ExitNode { Table, Fingerprint, Published, ExitAddresses, }