diff --git a/src/apis/context/auth_key/handlers.rs b/src/apis/context/auth_key/handlers.rs new file mode 100644 index 000000000..af78b3f4c --- /dev/null +++ b/src/apis/context/auth_key/handlers.rs @@ -0,0 +1,46 @@ +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; + +use axum::extract::{Path, State}; +use axum::response::Response; +use serde::Deserialize; + +use super::responses::{ + auth_key_response, failed_to_delete_key_response, failed_to_generate_key_response, failed_to_reload_keys_response, +}; +use crate::apis::context::auth_key::resources::AuthKey; +use crate::apis::responses::{invalid_auth_key_param_response, ok_response}; +use crate::tracker::auth::Key; +use crate::tracker::Tracker; + +pub async fn generate_auth_key_handler(State(tracker): State>, Path(seconds_valid_or_key): Path) -> Response { + let seconds_valid = seconds_valid_or_key; + match tracker.generate_auth_key(Duration::from_secs(seconds_valid)).await { + Ok(auth_key) => auth_key_response(&AuthKey::from(auth_key)), + Err(e) => failed_to_generate_key_response(e), + } +} + +#[derive(Deserialize)] +pub struct KeyParam(String); + +pub async fn delete_auth_key_handler( + State(tracker): State>, + Path(seconds_valid_or_key): Path, +) -> Response { + match Key::from_str(&seconds_valid_or_key.0) { + Err(_) => invalid_auth_key_param_response(&seconds_valid_or_key.0), + Ok(key) => match tracker.remove_auth_key(&key.to_string()).await { + Ok(_) => ok_response(), + Err(e) => failed_to_delete_key_response(e), + }, + } +} + +pub async fn reload_keys_handler(State(tracker): State>) -> Response { + match tracker.load_keys_from_database().await { + Ok(_) => ok_response(), + Err(e) => failed_to_reload_keys_response(e), + } +} diff --git a/src/apis/context/auth_key/mod.rs b/src/apis/context/auth_key/mod.rs new file mode 100644 index 000000000..746a2f064 --- /dev/null +++ b/src/apis/context/auth_key/mod.rs @@ -0,0 +1,4 @@ +pub mod handlers; +pub mod resources; +pub mod responses; +pub mod routes; diff --git a/src/apis/resources/auth_key.rs b/src/apis/context/auth_key/resources.rs similarity index 100% rename from src/apis/resources/auth_key.rs rename to src/apis/context/auth_key/resources.rs diff --git a/src/apis/context/auth_key/responses.rs b/src/apis/context/auth_key/responses.rs new file mode 100644 index 000000000..8c1bf58dc --- /dev/null +++ b/src/apis/context/auth_key/responses.rs @@ -0,0 +1,35 @@ +use std::error::Error; + +use axum::http::{header, StatusCode}; +use axum::response::{IntoResponse, Response}; + +use crate::apis::context::auth_key::resources::AuthKey; +use crate::apis::responses::unhandled_rejection_response; + +/// # Panics +/// +/// Will panic if it can't convert the `AuthKey` resource to json +#[must_use] +pub fn auth_key_response(auth_key: &AuthKey) -> Response { + ( + StatusCode::OK, + [(header::CONTENT_TYPE, "application/json; charset=utf-8")], + serde_json::to_string(auth_key).unwrap(), + ) + .into_response() +} + +#[must_use] +pub fn failed_to_generate_key_response(e: E) -> Response { + unhandled_rejection_response(format!("failed to generate key: {e}")) +} + +#[must_use] +pub fn failed_to_delete_key_response(e: E) -> Response { + unhandled_rejection_response(format!("failed to delete key: {e}")) +} + +#[must_use] +pub fn failed_to_reload_keys_response(e: E) -> Response { + unhandled_rejection_response(format!("failed to reload keys: {e}")) +} diff --git a/src/apis/context/auth_key/routes.rs b/src/apis/context/auth_key/routes.rs new file mode 100644 index 000000000..2a4f5b9dd --- /dev/null +++ b/src/apis/context/auth_key/routes.rs @@ -0,0 +1,25 @@ +use std::sync::Arc; + +use axum::routing::{get, post}; +use axum::Router; + +use super::handlers::{delete_auth_key_handler, generate_auth_key_handler, reload_keys_handler}; +use crate::tracker::Tracker; + +pub fn add(router: Router, tracker: Arc) -> Router { + // Keys + router + .route( + // code-review: Axum does not allow two routes with the same path but different path variable name. + // In the new major API version, `seconds_valid` should be a POST form field so that we will have two paths: + // POST /api/key + // DELETE /api/key/:key + "/api/key/:seconds_valid_or_key", + post(generate_auth_key_handler) + .with_state(tracker.clone()) + .delete(delete_auth_key_handler) + .with_state(tracker.clone()), + ) + // Keys command + .route("/api/keys/reload", get(reload_keys_handler).with_state(tracker)) +} diff --git a/src/apis/resources/mod.rs b/src/apis/context/mod.rs similarity index 72% rename from src/apis/resources/mod.rs rename to src/apis/context/mod.rs index bf3ce273b..6d3fb7566 100644 --- a/src/apis/resources/mod.rs +++ b/src/apis/context/mod.rs @@ -1,4 +1,4 @@ pub mod auth_key; -pub mod peer; pub mod stats; pub mod torrent; +pub mod whitelist; diff --git a/src/apis/context/stats/handlers.rs b/src/apis/context/stats/handlers.rs new file mode 100644 index 000000000..e93e65996 --- /dev/null +++ b/src/apis/context/stats/handlers.rs @@ -0,0 +1,13 @@ +use std::sync::Arc; + +use axum::extract::State; +use axum::response::Json; + +use super::resources::Stats; +use super::responses::stats_response; +use crate::tracker::services::statistics::get_metrics; +use crate::tracker::Tracker; + +pub async fn get_stats_handler(State(tracker): State>) -> Json { + stats_response(get_metrics(tracker.clone()).await) +} diff --git a/src/apis/context/stats/mod.rs b/src/apis/context/stats/mod.rs new file mode 100644 index 000000000..746a2f064 --- /dev/null +++ b/src/apis/context/stats/mod.rs @@ -0,0 +1,4 @@ +pub mod handlers; +pub mod resources; +pub mod responses; +pub mod routes; diff --git a/src/apis/resources/stats.rs b/src/apis/context/stats/resources.rs similarity index 100% rename from src/apis/resources/stats.rs rename to src/apis/context/stats/resources.rs diff --git a/src/apis/context/stats/responses.rs b/src/apis/context/stats/responses.rs new file mode 100644 index 000000000..ea9a2480a --- /dev/null +++ b/src/apis/context/stats/responses.rs @@ -0,0 +1,8 @@ +use axum::response::Json; + +use super::resources::Stats; +use crate::tracker::services::statistics::TrackerMetrics; + +pub fn stats_response(tracker_metrics: TrackerMetrics) -> Json { + Json(Stats::from(tracker_metrics)) +} diff --git a/src/apis/context/stats/routes.rs b/src/apis/context/stats/routes.rs new file mode 100644 index 000000000..8791ed25a --- /dev/null +++ b/src/apis/context/stats/routes.rs @@ -0,0 +1,11 @@ +use std::sync::Arc; + +use axum::routing::get; +use axum::Router; + +use super::handlers::get_stats_handler; +use crate::tracker::Tracker; + +pub fn add(router: Router, tracker: Arc) -> Router { + router.route("/api/stats", get(get_stats_handler).with_state(tracker)) +} diff --git a/src/apis/context/torrent/handlers.rs b/src/apis/context/torrent/handlers.rs new file mode 100644 index 000000000..1a8280e75 --- /dev/null +++ b/src/apis/context/torrent/handlers.rs @@ -0,0 +1,59 @@ +use std::fmt; +use std::str::FromStr; +use std::sync::Arc; + +use axum::extract::{Path, Query, State}; +use axum::response::{IntoResponse, Json, Response}; +use serde::{de, Deserialize, Deserializer}; + +use super::resources::torrent::ListItem; +use super::responses::{torrent_info_response, torrent_list_response, torrent_not_known_response}; +use crate::apis::responses::invalid_info_hash_param_response; +use crate::apis::InfoHashParam; +use crate::protocol::info_hash::InfoHash; +use crate::tracker::services::torrent::{get_torrent_info, get_torrents, Pagination}; +use crate::tracker::Tracker; + +pub async fn get_torrent_handler(State(tracker): State>, Path(info_hash): Path) -> Response { + match InfoHash::from_str(&info_hash.0) { + Err(_) => invalid_info_hash_param_response(&info_hash.0), + Ok(info_hash) => match get_torrent_info(tracker.clone(), &info_hash).await { + Some(info) => torrent_info_response(info).into_response(), + None => torrent_not_known_response(), + }, + } +} + +#[derive(Deserialize)] +pub struct PaginationParams { + #[serde(default, deserialize_with = "empty_string_as_none")] + pub offset: Option, + pub limit: Option, +} + +pub async fn get_torrents_handler( + State(tracker): State>, + pagination: Query, +) -> Json> { + torrent_list_response( + &get_torrents( + tracker.clone(), + &Pagination::new_with_options(pagination.0.offset, pagination.0.limit), + ) + .await, + ) +} + +/// Serde deserialization decorator to map empty Strings to None, +fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: FromStr, + T::Err: fmt::Display, +{ + let opt = Option::::deserialize(de)?; + match opt.as_deref() { + None | Some("") => Ok(None), + Some(s) => FromStr::from_str(s).map_err(de::Error::custom).map(Some), + } +} diff --git a/src/apis/context/torrent/mod.rs b/src/apis/context/torrent/mod.rs new file mode 100644 index 000000000..746a2f064 --- /dev/null +++ b/src/apis/context/torrent/mod.rs @@ -0,0 +1,4 @@ +pub mod handlers; +pub mod resources; +pub mod responses; +pub mod routes; diff --git a/src/apis/context/torrent/resources/mod.rs b/src/apis/context/torrent/resources/mod.rs new file mode 100644 index 000000000..46d62aac5 --- /dev/null +++ b/src/apis/context/torrent/resources/mod.rs @@ -0,0 +1,2 @@ +pub mod peer; +pub mod torrent; diff --git a/src/apis/resources/peer.rs b/src/apis/context/torrent/resources/peer.rs similarity index 100% rename from src/apis/resources/peer.rs rename to src/apis/context/torrent/resources/peer.rs diff --git a/src/apis/resources/torrent.rs b/src/apis/context/torrent/resources/torrent.rs similarity index 96% rename from src/apis/resources/torrent.rs rename to src/apis/context/torrent/resources/torrent.rs index 3d8b2f427..1099dc923 100644 --- a/src/apis/resources/torrent.rs +++ b/src/apis/context/torrent/resources/torrent.rs @@ -74,8 +74,9 @@ mod tests { use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; - use crate::apis::resources::peer::Peer; - use crate::apis::resources::torrent::{ListItem, Torrent}; + use super::Torrent; + use crate::apis::context::torrent::resources::peer::Peer; + use crate::apis::context::torrent::resources::torrent::ListItem; use crate::protocol::clock::DurationSinceUnixEpoch; use crate::protocol::info_hash::InfoHash; use crate::tracker::peer; diff --git a/src/apis/context/torrent/responses.rs b/src/apis/context/torrent/responses.rs new file mode 100644 index 000000000..48e3c6e7f --- /dev/null +++ b/src/apis/context/torrent/responses.rs @@ -0,0 +1,18 @@ +use axum::response::{IntoResponse, Json, Response}; +use serde_json::json; + +use super::resources::torrent::{ListItem, Torrent}; +use crate::tracker::services::torrent::{BasicInfo, Info}; + +pub fn torrent_list_response(basic_infos: &[BasicInfo]) -> Json> { + Json(ListItem::new_vec(basic_infos)) +} + +pub fn torrent_info_response(info: Info) -> Json { + Json(Torrent::from(info)) +} + +#[must_use] +pub fn torrent_not_known_response() -> Response { + Json(json!("torrent not known")).into_response() +} diff --git a/src/apis/context/torrent/routes.rs b/src/apis/context/torrent/routes.rs new file mode 100644 index 000000000..234f17223 --- /dev/null +++ b/src/apis/context/torrent/routes.rs @@ -0,0 +1,17 @@ +use std::sync::Arc; + +use axum::routing::get; +use axum::Router; + +use super::handlers::{get_torrent_handler, get_torrents_handler}; +use crate::tracker::Tracker; + +pub fn add(router: Router, tracker: Arc) -> Router { + // Torrents + router + .route( + "/api/torrent/:info_hash", + get(get_torrent_handler).with_state(tracker.clone()), + ) + .route("/api/torrents", get(get_torrents_handler).with_state(tracker)) +} diff --git a/src/apis/context/whitelist/handlers.rs b/src/apis/context/whitelist/handlers.rs new file mode 100644 index 000000000..c1e90a509 --- /dev/null +++ b/src/apis/context/whitelist/handlers.rs @@ -0,0 +1,46 @@ +use std::str::FromStr; +use std::sync::Arc; + +use axum::extract::{Path, State}; +use axum::response::Response; + +use super::responses::{ + failed_to_reload_whitelist_response, failed_to_remove_torrent_from_whitelist_response, failed_to_whitelist_torrent_response, +}; +use crate::apis::responses::{invalid_info_hash_param_response, ok_response}; +use crate::apis::InfoHashParam; +use crate::protocol::info_hash::InfoHash; +use crate::tracker::Tracker; + +pub async fn add_torrent_to_whitelist_handler( + State(tracker): State>, + Path(info_hash): Path, +) -> Response { + match InfoHash::from_str(&info_hash.0) { + Err(_) => invalid_info_hash_param_response(&info_hash.0), + Ok(info_hash) => match tracker.add_torrent_to_whitelist(&info_hash).await { + Ok(_) => ok_response(), + Err(e) => failed_to_whitelist_torrent_response(e), + }, + } +} + +pub async fn remove_torrent_from_whitelist_handler( + State(tracker): State>, + Path(info_hash): Path, +) -> Response { + match InfoHash::from_str(&info_hash.0) { + Err(_) => invalid_info_hash_param_response(&info_hash.0), + Ok(info_hash) => match tracker.remove_torrent_from_whitelist(&info_hash).await { + Ok(_) => ok_response(), + Err(e) => failed_to_remove_torrent_from_whitelist_response(e), + }, + } +} + +pub async fn reload_whitelist_handler(State(tracker): State>) -> Response { + match tracker.load_whitelist_from_database().await { + Ok(_) => ok_response(), + Err(e) => failed_to_reload_whitelist_response(e), + } +} diff --git a/src/apis/context/whitelist/mod.rs b/src/apis/context/whitelist/mod.rs new file mode 100644 index 000000000..f6f000f34 --- /dev/null +++ b/src/apis/context/whitelist/mod.rs @@ -0,0 +1,3 @@ +pub mod handlers; +pub mod responses; +pub mod routes; diff --git a/src/apis/context/whitelist/responses.rs b/src/apis/context/whitelist/responses.rs new file mode 100644 index 000000000..dd2727898 --- /dev/null +++ b/src/apis/context/whitelist/responses.rs @@ -0,0 +1,20 @@ +use std::error::Error; + +use axum::response::Response; + +use crate::apis::responses::unhandled_rejection_response; + +#[must_use] +pub fn failed_to_remove_torrent_from_whitelist_response(e: E) -> Response { + unhandled_rejection_response(format!("failed to remove torrent from whitelist: {e}")) +} + +#[must_use] +pub fn failed_to_whitelist_torrent_response(e: E) -> Response { + unhandled_rejection_response(format!("failed to whitelist torrent: {e}")) +} + +#[must_use] +pub fn failed_to_reload_whitelist_response(e: E) -> Response { + unhandled_rejection_response(format!("failed to reload whitelist: {e}")) +} diff --git a/src/apis/context/whitelist/routes.rs b/src/apis/context/whitelist/routes.rs new file mode 100644 index 000000000..1349f8bc1 --- /dev/null +++ b/src/apis/context/whitelist/routes.rs @@ -0,0 +1,22 @@ +use std::sync::Arc; + +use axum::routing::{delete, get, post}; +use axum::Router; + +use super::handlers::{add_torrent_to_whitelist_handler, reload_whitelist_handler, remove_torrent_from_whitelist_handler}; +use crate::tracker::Tracker; + +pub fn add(router: Router, tracker: Arc) -> Router { + router + // Whitelisted torrents + .route( + "/api/whitelist/:info_hash", + post(add_torrent_to_whitelist_handler).with_state(tracker.clone()), + ) + .route( + "/api/whitelist/:info_hash", + delete(remove_torrent_from_whitelist_handler).with_state(tracker.clone()), + ) + // Whitelist commands + .route("/api/whitelist/reload", get(reload_whitelist_handler).with_state(tracker)) +} diff --git a/src/apis/handlers.rs b/src/apis/handlers.rs deleted file mode 100644 index 410def39b..000000000 --- a/src/apis/handlers.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::fmt; -use std::str::FromStr; -use std::sync::Arc; -use std::time::Duration; - -use axum::extract::{Path, Query, State}; -use axum::response::{IntoResponse, Json, Response}; -use serde::{de, Deserialize, Deserializer}; - -use super::responses::{ - 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_response, -}; -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::Key; -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>) -> Json { - stats_response(get_metrics(tracker.clone()).await) -} - -#[derive(Deserialize)] -pub struct InfoHashParam(String); - -pub async fn get_torrent_handler(State(tracker): State>, Path(info_hash): Path) -> Response { - match InfoHash::from_str(&info_hash.0) { - Err(_) => invalid_info_hash_param_response(&info_hash.0), - Ok(info_hash) => match get_torrent_info(tracker.clone(), &info_hash).await { - Some(info) => torrent_info_response(info).into_response(), - None => torrent_not_known_response(), - }, - } -} - -#[derive(Deserialize)] -pub struct PaginationParams { - #[serde(default, deserialize_with = "empty_string_as_none")] - pub offset: Option, - pub limit: Option, -} - -pub async fn get_torrents_handler( - State(tracker): State>, - pagination: Query, -) -> Json> { - torrent_list_response( - &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>, - Path(info_hash): Path, -) -> Response { - match InfoHash::from_str(&info_hash.0) { - Err(_) => invalid_info_hash_param_response(&info_hash.0), - Ok(info_hash) => match tracker.add_torrent_to_whitelist(&info_hash).await { - Ok(_) => ok_response(), - Err(e) => failed_to_whitelist_torrent_response(e), - }, - } -} - -pub async fn remove_torrent_from_whitelist_handler( - State(tracker): State>, - Path(info_hash): Path, -) -> Response { - match InfoHash::from_str(&info_hash.0) { - Err(_) => invalid_info_hash_param_response(&info_hash.0), - Ok(info_hash) => match tracker.remove_torrent_from_whitelist(&info_hash).await { - Ok(_) => ok_response(), - Err(e) => failed_to_remove_torrent_from_whitelist_response(e), - }, - } -} - -pub async fn reload_whitelist_handler(State(tracker): State>) -> Response { - match tracker.load_whitelist_from_database().await { - Ok(_) => ok_response(), - Err(e) => failed_to_reload_whitelist_response(e), - } -} - -pub async fn generate_auth_key_handler(State(tracker): State>, Path(seconds_valid_or_key): Path) -> Response { - let seconds_valid = seconds_valid_or_key; - match tracker.generate_auth_key(Duration::from_secs(seconds_valid)).await { - Ok(auth_key) => auth_key_response(&AuthKey::from(auth_key)), - Err(e) => failed_to_generate_key_response(e), - } -} - -#[derive(Deserialize)] -pub struct KeyParam(String); - -pub async fn delete_auth_key_handler( - State(tracker): State>, - Path(seconds_valid_or_key): Path, -) -> Response { - match Key::from_str(&seconds_valid_or_key.0) { - Err(_) => invalid_auth_key_param_response(&seconds_valid_or_key.0), - Ok(key) => match tracker.remove_auth_key(&key.to_string()).await { - Ok(_) => ok_response(), - Err(e) => failed_to_delete_key_response(e), - }, - } -} - -pub async fn reload_keys_handler(State(tracker): State>) -> Response { - match tracker.load_keys_from_database().await { - Ok(_) => ok_response(), - Err(e) => failed_to_reload_keys_response(e), - } -} - -/// Serde deserialization decorator to map empty Strings to None, -fn empty_string_as_none<'de, D, T>(de: D) -> Result, D::Error> -where - D: Deserializer<'de>, - T: FromStr, - T::Err: fmt::Display, -{ - let opt = Option::::deserialize(de)?; - match opt.as_deref() { - None | Some("") => Ok(None), - Some(s) => FromStr::from_str(s).map_err(de::Error::custom).map(Some), - } -} diff --git a/src/apis/mod.rs b/src/apis/mod.rs index a646d5543..fd7fdb6e5 100644 --- a/src/apis/mod.rs +++ b/src/apis/mod.rs @@ -1,6 +1,10 @@ -pub mod handlers; +pub mod context; pub mod middlewares; -pub mod resources; pub mod responses; pub mod routes; pub mod server; + +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct InfoHashParam(pub String); diff --git a/src/apis/responses.rs b/src/apis/responses.rs index c0a6cbcf8..4a9c39bf9 100644 --- a/src/apis/responses.rs +++ b/src/apis/responses.rs @@ -1,15 +1,6 @@ -use std::error::Error; - use axum::http::{header, StatusCode}; -use axum::response::{IntoResponse, Json, Response}; +use axum::response::{IntoResponse, Response}; use serde::Serialize; -use serde_json::json; - -use crate::apis::resources::auth_key::AuthKey; -use crate::apis::resources::stats::Stats; -use crate::apis::resources::torrent::{ListItem, Torrent}; -use crate::tracker::services::statistics::TrackerMetrics; -use crate::tracker::services::torrent::{BasicInfo, Info}; /* code-review: When Axum cannot parse a path or query param it shows a message like this: @@ -38,36 +29,6 @@ pub enum ActionStatus<'a> { Err { reason: std::borrow::Cow<'a, str> }, } -// Resource responses - -#[must_use] -pub fn stats_response(tracker_metrics: TrackerMetrics) -> Json { - Json(Stats::from(tracker_metrics)) -} - -#[must_use] -pub fn torrent_list_response(basic_infos: &[BasicInfo]) -> Json> { - Json(ListItem::new_vec(basic_infos)) -} - -#[must_use] -pub fn torrent_info_response(info: Info) -> Json { - Json(Torrent::from(info)) -} - -/// # Panics -/// -/// Will panic if it can't convert the `AuthKey` resource to json -#[must_use] -pub fn auth_key_response(auth_key: &AuthKey) -> Response { - ( - StatusCode::OK, - [(header::CONTENT_TYPE, "application/json; charset=utf-8")], - serde_json::to_string(auth_key).unwrap(), - ) - .into_response() -} - // OK response /// # Panics @@ -106,41 +67,6 @@ fn bad_request_response(body: &str) -> Response { .into_response() } -#[must_use] -pub fn torrent_not_known_response() -> Response { - Json(json!("torrent not known")).into_response() -} - -#[must_use] -pub fn failed_to_remove_torrent_from_whitelist_response(e: E) -> Response { - unhandled_rejection_response(format!("failed to remove torrent from whitelist: {e}")) -} - -#[must_use] -pub fn failed_to_whitelist_torrent_response(e: E) -> Response { - unhandled_rejection_response(format!("failed to whitelist torrent: {e}")) -} - -#[must_use] -pub fn failed_to_reload_whitelist_response(e: E) -> Response { - unhandled_rejection_response(format!("failed to reload whitelist: {e}")) -} - -#[must_use] -pub fn failed_to_generate_key_response(e: E) -> Response { - unhandled_rejection_response(format!("failed to generate key: {e}")) -} - -#[must_use] -pub fn failed_to_delete_key_response(e: E) -> Response { - unhandled_rejection_response(format!("failed to delete key: {e}")) -} - -#[must_use] -pub fn failed_to_reload_keys_response(e: E) -> Response { - unhandled_rejection_response(format!("failed to reload keys: {e}")) -} - /// This error response is to keep backward compatibility with the old API. /// It should be a plain text or json. #[must_use] diff --git a/src/apis/routes.rs b/src/apis/routes.rs index ecc51090c..c567e50da 100644 --- a/src/apis/routes.rs +++ b/src/apis/routes.rs @@ -1,53 +1,19 @@ use std::sync::Arc; -use axum::routing::{delete, get, post}; use axum::{middleware, Router}; -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 super::context::{auth_key, stats, torrent, whitelist}; use super::middlewares::auth::auth; use crate::tracker::Tracker; #[allow(clippy::needless_pass_by_value)] pub fn router(tracker: Arc) -> Router { - Router::new() - // Stats - .route("/api/stats", get(get_stats_handler).with_state(tracker.clone())) - // Torrents - .route( - "/api/torrent/:info_hash", - get(get_torrent_handler).with_state(tracker.clone()), - ) - .route("/api/torrents", get(get_torrents_handler).with_state(tracker.clone())) - // Whitelisted torrents - .route( - "/api/whitelist/:info_hash", - post(add_torrent_to_whitelist_handler).with_state(tracker.clone()), - ) - .route( - "/api/whitelist/:info_hash", - delete(remove_torrent_from_whitelist_handler).with_state(tracker.clone()), - ) - // Whitelist command - .route( - "/api/whitelist/reload", - get(reload_whitelist_handler).with_state(tracker.clone()), - ) - // Keys - .route( - // code-review: Axum does not allow two routes with the same path but different path variable name. - // In the new major API version, `seconds_valid` should be a POST form field so that we will have two paths: - // POST /api/key - // DELETE /api/key/:key - "/api/key/:seconds_valid_or_key", - post(generate_auth_key_handler) - .with_state(tracker.clone()) - .delete(delete_auth_key_handler) - .with_state(tracker.clone()), - ) - // Keys command - .route("/api/keys/reload", get(reload_keys_handler).with_state(tracker.clone())) - .layer(middleware::from_fn_with_state(tracker.config.clone(), auth)) + let router = Router::new(); + + let router = auth_key::routes::add(router, tracker.clone()); + let router = stats::routes::add(router, tracker.clone()); + let router = whitelist::routes::add(router, tracker.clone()); + let router = torrent::routes::add(router, tracker.clone()); + + router.layer(middleware::from_fn_with_state(tracker.config.clone(), auth)) } diff --git a/tests/api/asserts.rs b/tests/api/asserts.rs index 5a4abfb62..c7567e6fe 100644 --- a/tests/api/asserts.rs +++ b/tests/api/asserts.rs @@ -1,9 +1,9 @@ // code-review: should we use macros to return the exact line where the assert fails? use reqwest::Response; -use torrust_tracker::apis::resources::auth_key::AuthKey; -use torrust_tracker::apis::resources::stats::Stats; -use torrust_tracker::apis::resources::torrent::{ListItem, Torrent}; +use torrust_tracker::apis::context::auth_key::resources::AuthKey; +use torrust_tracker::apis::context::stats::resources::Stats; +use torrust_tracker::apis::context::torrent::resources::torrent::{ListItem, Torrent}; // Resource responses diff --git a/tests/tracker_api.rs b/tests/tracker_api.rs index dac5907c2..ff4eb295b 100644 --- a/tests/tracker_api.rs +++ b/tests/tracker_api.rs @@ -132,7 +132,7 @@ mod tracker_apis { mod for_stats_resources { use std::str::FromStr; - use torrust_tracker::apis::resources::stats::Stats; + use torrust_tracker::apis::context::stats::resources::Stats; use torrust_tracker::protocol::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; @@ -206,8 +206,8 @@ mod tracker_apis { mod for_torrent_resources { use std::str::FromStr; - use torrust_tracker::apis::resources::torrent::Torrent; - use torrust_tracker::apis::resources::{self, torrent}; + use torrust_tracker::apis::context::torrent::resources::peer::Peer; + use torrust_tracker::apis::context::torrent::resources::torrent::{self, Torrent}; use torrust_tracker::protocol::info_hash::InfoHash; use torrust_tracker_test_helpers::configuration; @@ -383,7 +383,7 @@ mod tracker_apis { seeders: 1, completed: 0, leechers: 0, - peers: Some(vec![resources::peer::Peer::from(peer)]), + peers: Some(vec![Peer::from(peer)]), }, ) .await;