Verified Commit b91970d4 authored by Mattia Righetti's avatar Mattia Righetti
Browse files

feat(exit_addresses): impl endpoint

parent 8a2fd536
Loading
Loading
Loading
Loading
+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, &params).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, &params).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))
}
+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;
@@ -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::*;
+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())
}
+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::*;
+8 −0
Original line number Diff line number Diff line
@@ -48,3 +48,11 @@ pub enum ServerDescriptor {
    Contact,
    Ipv6DefaultPolicy,
}

#[derive(Iden)]
pub enum ExitNode {
    Table,
    Fingerprint,
    Published,
    ExitAddresses,
}
Loading