forked from torrust/torrust-tracker
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: [torrust#1159] extract new package tracker api client
- Loading branch information
1 parent
f6aca40
commit a1ded65
Showing
12 changed files
with
345 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,7 +16,7 @@ rust-version.workspace = true | |
version.workspace = true | ||
|
||
[lib] | ||
name = "torrust_tracker_lib" | ||
name = "torrust_tracker_lib" | ||
|
||
[workspace.package] | ||
authors = ["Nautilus Cyberneering <[email protected]>, Mick van Dijke <[email protected]>"] | ||
|
@@ -108,6 +108,7 @@ members = [ | |
"packages/primitives", | ||
"packages/test-helpers", | ||
"packages/torrent-repository", | ||
"packages/tracker-api-client", | ||
"packages/tracker-client", | ||
] | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
[package] | ||
description = "A library to interact with the Torrust Tracker REST API." | ||
keywords = ["bittorrent", "client", "tracker"] | ||
license = "LGPL-3.0" | ||
name = "torrust-tracker-api-client" | ||
readme = "README.md" | ||
|
||
authors.workspace = true | ||
documentation.workspace = true | ||
edition.workspace = true | ||
homepage.workspace = true | ||
publish.workspace = true | ||
repository.workspace = true | ||
rust-version.workspace = true | ||
version.workspace = true | ||
|
||
[dependencies] | ||
hyper = "1" | ||
reqwest = { version = "0", features = ["json"] } | ||
serde = { version = "1", features = ["derive"] } | ||
uuid = { version = "1", features = ["v4"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Torrust Tracker API Client | ||
|
||
A library to interact with the Torrust Tracker REST API. | ||
|
||
## License | ||
|
||
**Copyright (c) 2024 The Torrust Developers.** | ||
|
||
This program is free software: you can redistribute it and/or modify it under the terms of the [GNU Lesser General Public License][LGPL_3_0] as published by the [Free Software Foundation][FSF], version 3. | ||
|
||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the [GNU Lesser General Public License][LGPL_3_0] for more details. | ||
|
||
You should have received a copy of the *GNU Lesser General Public License* along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
||
Some files include explicit copyright notices and/or license notices. | ||
|
||
### Legacy Exception | ||
|
||
For prosperity, versions of Torrust BitTorrent Tracker Client that are older than five years are automatically granted the [MIT-0][MIT_0] license in addition to the existing [LGPL-3.0-only][LGPL_3_0] license. | ||
|
||
[LGPL_3_0]: ./LICENSE | ||
[MIT_0]: ./docs/licenses/LICENSE-MIT_0 | ||
[FSF]: https://www.fsf.org/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
MIT No Attribution | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
software and associated documentation files (the "Software"), to deal in the Software | ||
without restriction, including without limitation the rights to use, copy, modify, | ||
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | ||
permit persons to whom the Software is furnished to do so. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | ||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | ||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | ||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | ||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
pub type ReqwestQuery = Vec<ReqwestQueryParam>; | ||
pub type ReqwestQueryParam = (String, String); | ||
|
||
/// URL Query component | ||
#[derive(Default, Debug)] | ||
pub struct Query { | ||
params: Vec<QueryParam>, | ||
} | ||
|
||
impl Query { | ||
#[must_use] | ||
pub fn empty() -> Self { | ||
Self { params: vec![] } | ||
} | ||
|
||
#[must_use] | ||
pub fn params(params: Vec<QueryParam>) -> Self { | ||
Self { params } | ||
} | ||
|
||
pub fn add_param(&mut self, param: QueryParam) { | ||
self.params.push(param); | ||
} | ||
} | ||
|
||
impl From<Query> for ReqwestQuery { | ||
fn from(url_search_params: Query) -> Self { | ||
url_search_params | ||
.params | ||
.iter() | ||
.map(|param| ReqwestQueryParam::from((*param).clone())) | ||
.collect() | ||
} | ||
} | ||
|
||
/// URL query param | ||
#[derive(Clone, Debug)] | ||
pub struct QueryParam { | ||
name: String, | ||
value: String, | ||
} | ||
|
||
impl QueryParam { | ||
#[must_use] | ||
pub fn new(name: &str, value: &str) -> Self { | ||
Self { | ||
name: name.to_string(), | ||
value: value.to_string(), | ||
} | ||
} | ||
} | ||
|
||
impl From<QueryParam> for ReqwestQueryParam { | ||
fn from(param: QueryParam) -> Self { | ||
(param.name, param.value) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod http; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
#[must_use] | ||
pub fn connection_with_invalid_token(bind_address: &str) -> ConnectionInfo { | ||
ConnectionInfo::authenticated(bind_address, "invalid token") | ||
} | ||
|
||
#[must_use] | ||
pub fn connection_with_no_token(bind_address: &str) -> ConnectionInfo { | ||
ConnectionInfo::anonymous(bind_address) | ||
} | ||
|
||
#[derive(Clone)] | ||
pub struct ConnectionInfo { | ||
pub bind_address: String, | ||
pub api_token: Option<String>, | ||
} | ||
|
||
impl ConnectionInfo { | ||
#[must_use] | ||
pub fn authenticated(bind_address: &str, api_token: &str) -> Self { | ||
Self { | ||
bind_address: bind_address.to_string(), | ||
api_token: Some(api_token.to_string()), | ||
} | ||
} | ||
|
||
#[must_use] | ||
pub fn anonymous(bind_address: &str) -> Self { | ||
Self { | ||
bind_address: bind_address.to_string(), | ||
api_token: None, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
pub mod common; | ||
pub mod connection_info; | ||
pub mod v1; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
use hyper::HeaderMap; | ||
use reqwest::Response; | ||
use serde::Serialize; | ||
use uuid::Uuid; | ||
|
||
use crate::common::http::{Query, QueryParam, ReqwestQuery}; | ||
use crate::connection_info::ConnectionInfo; | ||
|
||
/// API Client | ||
pub struct Client { | ||
connection_info: ConnectionInfo, | ||
base_path: String, | ||
} | ||
|
||
impl Client { | ||
#[must_use] | ||
pub fn new(connection_info: ConnectionInfo) -> Self { | ||
Self { | ||
connection_info, | ||
base_path: "/api/v1/".to_string(), | ||
} | ||
} | ||
|
||
pub async fn generate_auth_key(&self, seconds_valid: i32, headers: Option<HeaderMap>) -> Response { | ||
self.post_empty(&format!("key/{}", &seconds_valid), headers).await | ||
} | ||
|
||
pub async fn add_auth_key(&self, add_key_form: AddKeyForm, headers: Option<HeaderMap>) -> Response { | ||
self.post_form("keys", &add_key_form, headers).await | ||
} | ||
|
||
pub async fn delete_auth_key(&self, key: &str, headers: Option<HeaderMap>) -> Response { | ||
self.delete(&format!("key/{}", &key), headers).await | ||
} | ||
|
||
pub async fn reload_keys(&self, headers: Option<HeaderMap>) -> Response { | ||
self.get("keys/reload", Query::default(), headers).await | ||
} | ||
|
||
pub async fn whitelist_a_torrent(&self, info_hash: &str, headers: Option<HeaderMap>) -> Response { | ||
self.post_empty(&format!("whitelist/{}", &info_hash), headers).await | ||
} | ||
|
||
pub async fn remove_torrent_from_whitelist(&self, info_hash: &str, headers: Option<HeaderMap>) -> Response { | ||
self.delete(&format!("whitelist/{}", &info_hash), headers).await | ||
} | ||
|
||
pub async fn reload_whitelist(&self, headers: Option<HeaderMap>) -> Response { | ||
self.get("whitelist/reload", Query::default(), headers).await | ||
} | ||
|
||
pub async fn get_torrent(&self, info_hash: &str, headers: Option<HeaderMap>) -> Response { | ||
self.get(&format!("torrent/{}", &info_hash), Query::default(), headers).await | ||
} | ||
|
||
pub async fn get_torrents(&self, params: Query, headers: Option<HeaderMap>) -> Response { | ||
self.get("torrents", params, headers).await | ||
} | ||
|
||
pub async fn get_tracker_statistics(&self, headers: Option<HeaderMap>) -> Response { | ||
self.get("stats", Query::default(), headers).await | ||
} | ||
|
||
pub async fn get(&self, path: &str, params: Query, headers: Option<HeaderMap>) -> Response { | ||
let mut query: Query = params; | ||
|
||
if let Some(token) = &self.connection_info.api_token { | ||
query.add_param(QueryParam::new("token", token)); | ||
}; | ||
|
||
self.get_request_with_query(path, query, headers).await | ||
} | ||
|
||
/// # Panics | ||
/// | ||
/// Will panic if the request can't be sent | ||
pub async fn post_empty(&self, path: &str, headers: Option<HeaderMap>) -> Response { | ||
let builder = reqwest::Client::new() | ||
.post(self.base_url(path).clone()) | ||
.query(&ReqwestQuery::from(self.query_with_token())); | ||
|
||
let builder = match headers { | ||
Some(headers) => builder.headers(headers), | ||
None => builder, | ||
}; | ||
|
||
builder.send().await.unwrap() | ||
} | ||
|
||
/// # Panics | ||
/// | ||
/// Will panic if the request can't be sent | ||
pub async fn post_form<T: Serialize + ?Sized>(&self, path: &str, form: &T, headers: Option<HeaderMap>) -> Response { | ||
let builder = reqwest::Client::new() | ||
.post(self.base_url(path).clone()) | ||
.query(&ReqwestQuery::from(self.query_with_token())) | ||
.json(&form); | ||
|
||
let builder = match headers { | ||
Some(headers) => builder.headers(headers), | ||
None => builder, | ||
}; | ||
|
||
builder.send().await.unwrap() | ||
} | ||
|
||
/// # Panics | ||
/// | ||
/// Will panic if the request can't be sent | ||
async fn delete(&self, path: &str, headers: Option<HeaderMap>) -> Response { | ||
let builder = reqwest::Client::new() | ||
.delete(self.base_url(path).clone()) | ||
.query(&ReqwestQuery::from(self.query_with_token())); | ||
|
||
let builder = match headers { | ||
Some(headers) => builder.headers(headers), | ||
None => builder, | ||
}; | ||
|
||
builder.send().await.unwrap() | ||
} | ||
|
||
pub async fn get_request_with_query(&self, path: &str, params: Query, headers: Option<HeaderMap>) -> Response { | ||
get(&self.base_url(path), Some(params), headers).await | ||
} | ||
|
||
pub async fn get_request(&self, path: &str) -> Response { | ||
get(&self.base_url(path), None, None).await | ||
} | ||
|
||
fn query_with_token(&self) -> Query { | ||
match &self.connection_info.api_token { | ||
Some(token) => Query::params([QueryParam::new("token", token)].to_vec()), | ||
None => Query::default(), | ||
} | ||
} | ||
|
||
fn base_url(&self, path: &str) -> String { | ||
format!("http://{}{}{path}", &self.connection_info.bind_address, &self.base_path) | ||
} | ||
} | ||
|
||
/// # Panics | ||
/// | ||
/// Will panic if the request can't be sent | ||
pub async fn get(path: &str, query: Option<Query>, headers: Option<HeaderMap>) -> Response { | ||
let builder = reqwest::Client::builder().build().unwrap(); | ||
|
||
let builder = match query { | ||
Some(params) => builder.get(path).query(&ReqwestQuery::from(params)), | ||
None => builder.get(path), | ||
}; | ||
|
||
let builder = match headers { | ||
Some(headers) => builder.headers(headers), | ||
None => builder, | ||
}; | ||
|
||
builder.send().await.unwrap() | ||
} | ||
|
||
/// Returns a `HeaderMap` with a request id header | ||
/// | ||
/// # Panics | ||
/// | ||
/// Will panic if the request ID can't be parsed into a string. | ||
#[must_use] | ||
pub fn headers_with_request_id(request_id: Uuid) -> HeaderMap { | ||
let mut headers = HeaderMap::new(); | ||
headers.insert("x-request-id", request_id.to_string().parse().unwrap()); | ||
headers | ||
} | ||
|
||
#[derive(Serialize, Debug)] | ||
pub struct AddKeyForm { | ||
#[serde(rename = "key")] | ||
pub opt_key: Option<String>, | ||
pub seconds_valid: Option<u64>, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
pub mod client; |