Skip to content

Commit

Permalink
refactor(api): [#143] extract api mods
Browse files Browse the repository at this point in the history
  • Loading branch information
josecelano committed Jan 13, 2023
1 parent 2a92b0a commit 0940463
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 134 deletions.
138 changes: 138 additions & 0 deletions src/apis/handlers.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

use axum::extract::{Path, Query, State};
use axum::response::{Json, Response};
use serde::{de, Deserialize, Deserializer};

use super::responses::{
response_auth_key, response_failed_to_delete_key, response_failed_to_generate_key, response_failed_to_reload_keys,
response_failed_to_reload_whitelist, response_failed_to_remove_torrent_from_whitelist, response_failed_to_whitelist_torrent,
response_invalid_auth_key_param, response_invalid_info_hash_param, response_ok, response_stats, response_torrent_info,
response_torrent_list, response_torrent_not_known,
};
use crate::apis::resources::auth_key::AuthKey;
use crate::apis::resources::stats::Stats;
use crate::apis::resources::torrent::ListItem;
use crate::protocol::info_hash::InfoHash;
use crate::tracker::auth::KeyId;
use crate::tracker::services::statistics::get_metrics;
use crate::tracker::services::torrent::{get_torrent_info, get_torrents, Pagination};
use crate::tracker::Tracker;

pub async fn get_stats_handler(State(tracker): State<Arc<Tracker>>) -> Json<Stats> {
response_stats(get_metrics(tracker.clone()).await)
}

#[derive(Deserialize)]
pub struct InfoHashParam(String);

pub async fn get_torrent_handler(State(tracker): State<Arc<Tracker>>, Path(info_hash): Path<InfoHashParam>) -> Response {
match InfoHash::from_str(&info_hash.0) {
Err(_) => response_invalid_info_hash_param(&info_hash.0),
Ok(info_hash) => match get_torrent_info(tracker.clone(), &info_hash).await {
Some(info) => response_torrent_info(info),
None => response_torrent_not_known(),
},
}
}

#[derive(Deserialize)]
pub struct PaginationParams {
#[serde(default, deserialize_with = "empty_string_as_none")]
pub offset: Option<u32>,
pub limit: Option<u32>,
}

pub async fn get_torrents_handler(
State(tracker): State<Arc<Tracker>>,
pagination: Query<PaginationParams>,
) -> Json<Vec<ListItem>> {
response_torrent_list(
&get_torrents(
tracker.clone(),
&Pagination::new_with_options(pagination.0.offset, pagination.0.limit),
)
.await,
)
}

pub async fn add_torrent_to_whitelist_handler(
State(tracker): State<Arc<Tracker>>,
Path(info_hash): Path<InfoHashParam>,
) -> Response {
match InfoHash::from_str(&info_hash.0) {
Err(_) => response_invalid_info_hash_param(&info_hash.0),
Ok(info_hash) => match tracker.add_torrent_to_whitelist(&info_hash).await {
Ok(..) => response_ok(),
Err(..) => response_failed_to_whitelist_torrent(),
},
}
}

pub async fn remove_torrent_from_whitelist_handler(
State(tracker): State<Arc<Tracker>>,
Path(info_hash): Path<InfoHashParam>,
) -> Response {
match InfoHash::from_str(&info_hash.0) {
Err(_) => response_invalid_info_hash_param(&info_hash.0),
Ok(info_hash) => match tracker.remove_torrent_from_whitelist(&info_hash).await {
Ok(..) => response_ok(),
Err(..) => response_failed_to_remove_torrent_from_whitelist(),
},
}
}

pub async fn reload_whitelist_handler(State(tracker): State<Arc<Tracker>>) -> Response {
match tracker.load_whitelist().await {
Ok(..) => response_ok(),
Err(..) => response_failed_to_reload_whitelist(),
}
}

pub async fn generate_auth_key_handler(State(tracker): State<Arc<Tracker>>, Path(seconds_valid_or_key): Path<u64>) -> Response {
let seconds_valid = seconds_valid_or_key;
match tracker.generate_auth_key(Duration::from_secs(seconds_valid)).await {
Ok(auth_key) => response_auth_key(&AuthKey::from(auth_key)),
Err(_) => response_failed_to_generate_key(),
}
}

#[derive(Deserialize)]
pub struct KeyIdParam(String);

pub async fn delete_auth_key_handler(
State(tracker): State<Arc<Tracker>>,
Path(seconds_valid_or_key): Path<KeyIdParam>,
) -> Response {
match KeyId::from_str(&seconds_valid_or_key.0) {
Err(_) => response_invalid_auth_key_param(&seconds_valid_or_key.0),
Ok(key_id) => match tracker.remove_auth_key(&key_id.to_string()).await {
Ok(_) => response_ok(),
Err(_) => response_failed_to_delete_key(),
},
}
}

pub async fn reload_keys_handler(State(tracker): State<Arc<Tracker>>) -> Response {
match tracker.load_keys().await {
Ok(..) => response_ok(),
Err(..) => response_failed_to_reload_keys(),
}
}

/// Serde deserialization decorator to map empty Strings to None,
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: fmt::Display,
{
let opt = Option::<String>::deserialize(de)?;
match opt.as_deref() {
None | Some("") => Ok(None),
Some(s) => FromStr::from_str(s).map_err(de::Error::custom).map(Some),
}
}
1 change: 1 addition & 0 deletions src/apis/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod handlers;
pub mod middlewares;
pub mod resources;
pub mod responses;
Expand Down
138 changes: 4 additions & 134 deletions src/apis/routes.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,13 @@
use std::fmt;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;

use axum::extract::{Path, Query, State};
use axum::response::{Json, Response};
use axum::routing::{delete, get, post};
use axum::{middleware, Router};
use serde::{de, Deserialize, Deserializer};

use super::middlewares::auth::auth;
use super::responses::{
response_auth_key, response_failed_to_delete_key, response_failed_to_generate_key, response_failed_to_reload_keys,
response_failed_to_reload_whitelist, response_failed_to_remove_torrent_from_whitelist, response_failed_to_whitelist_torrent,
response_invalid_auth_key_param, response_invalid_info_hash_param, response_ok, response_stats, response_torrent_info,
response_torrent_list, response_torrent_not_known,
use super::handlers::{
add_torrent_to_whitelist_handler, delete_auth_key_handler, generate_auth_key_handler, get_stats_handler, get_torrent_handler,
get_torrents_handler, reload_keys_handler, reload_whitelist_handler, remove_torrent_from_whitelist_handler,
};
use crate::apis::resources::auth_key::AuthKey;
use crate::apis::resources::stats::Stats;
use crate::apis::resources::torrent::ListItem;
use crate::protocol::info_hash::InfoHash;
use crate::tracker::auth::KeyId;
use crate::tracker::services::statistics::get_metrics;
use crate::tracker::services::torrent::{get_torrent_info, get_torrents, Pagination};
use super::middlewares::auth::auth;
use crate::tracker::Tracker;

/* code-review:
Expand Down Expand Up @@ -85,118 +70,3 @@ pub fn router(tracker: &Arc<Tracker>) -> Router {
.route("/api/keys/reload", get(reload_keys_handler).with_state(tracker.clone()))
.layer(middleware::from_fn_with_state(tracker.config.clone(), auth))
}

pub async fn get_stats_handler(State(tracker): State<Arc<Tracker>>) -> Json<Stats> {
response_stats(get_metrics(tracker.clone()).await)
}

#[derive(Deserialize)]
pub struct InfoHashParam(String);

pub async fn get_torrent_handler(State(tracker): State<Arc<Tracker>>, Path(info_hash): Path<InfoHashParam>) -> Response {
match InfoHash::from_str(&info_hash.0) {
Err(_) => response_invalid_info_hash_param(&info_hash.0),
Ok(info_hash) => match get_torrent_info(tracker.clone(), &info_hash).await {
Some(info) => response_torrent_info(info),
None => response_torrent_not_known(),
},
}
}

#[derive(Deserialize)]
pub struct PaginationParams {
#[serde(default, deserialize_with = "empty_string_as_none")]
pub offset: Option<u32>,
pub limit: Option<u32>,
}

pub async fn get_torrents_handler(
State(tracker): State<Arc<Tracker>>,
pagination: Query<PaginationParams>,
) -> Json<Vec<ListItem>> {
response_torrent_list(
&get_torrents(
tracker.clone(),
&Pagination::new_with_options(pagination.0.offset, pagination.0.limit),
)
.await,
)
}

pub async fn add_torrent_to_whitelist_handler(
State(tracker): State<Arc<Tracker>>,
Path(info_hash): Path<InfoHashParam>,
) -> Response {
match InfoHash::from_str(&info_hash.0) {
Err(_) => response_invalid_info_hash_param(&info_hash.0),
Ok(info_hash) => match tracker.add_torrent_to_whitelist(&info_hash).await {
Ok(..) => response_ok(),
Err(..) => response_failed_to_whitelist_torrent(),
},
}
}

pub async fn remove_torrent_from_whitelist_handler(
State(tracker): State<Arc<Tracker>>,
Path(info_hash): Path<InfoHashParam>,
) -> Response {
match InfoHash::from_str(&info_hash.0) {
Err(_) => response_invalid_info_hash_param(&info_hash.0),
Ok(info_hash) => match tracker.remove_torrent_from_whitelist(&info_hash).await {
Ok(..) => response_ok(),
Err(..) => response_failed_to_remove_torrent_from_whitelist(),
},
}
}

pub async fn reload_whitelist_handler(State(tracker): State<Arc<Tracker>>) -> Response {
match tracker.load_whitelist().await {
Ok(..) => response_ok(),
Err(..) => response_failed_to_reload_whitelist(),
}
}

pub async fn generate_auth_key_handler(State(tracker): State<Arc<Tracker>>, Path(seconds_valid_or_key): Path<u64>) -> Response {
let seconds_valid = seconds_valid_or_key;
match tracker.generate_auth_key(Duration::from_secs(seconds_valid)).await {
Ok(auth_key) => response_auth_key(&AuthKey::from(auth_key)),
Err(_) => response_failed_to_generate_key(),
}
}

#[derive(Deserialize)]
pub struct KeyIdParam(String);

pub async fn delete_auth_key_handler(
State(tracker): State<Arc<Tracker>>,
Path(seconds_valid_or_key): Path<KeyIdParam>,
) -> Response {
match KeyId::from_str(&seconds_valid_or_key.0) {
Err(_) => response_invalid_auth_key_param(&seconds_valid_or_key.0),
Ok(key_id) => match tracker.remove_auth_key(&key_id.to_string()).await {
Ok(_) => response_ok(),
Err(_) => response_failed_to_delete_key(),
},
}
}

pub async fn reload_keys_handler(State(tracker): State<Arc<Tracker>>) -> Response {
match tracker.load_keys().await {
Ok(..) => response_ok(),
Err(..) => response_failed_to_reload_keys(),
}
}

/// Serde deserialization decorator to map empty Strings to None,
fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
where
D: Deserializer<'de>,
T: FromStr,
T::Err: fmt::Display,
{
let opt = Option::<String>::deserialize(de)?;
match opt.as_deref() {
None | Some("") => Ok(None),
Some(s) => FromStr::from_str(s).map_err(de::Error::custom).map(Some),
}
}

0 comments on commit 0940463

Please sign in to comment.