Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
/data.db
/.vscode/launch.json
/storage/


/config/
/config.*/
7 changes: 4 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"[rust]": {
"editor.formatOnSave": true
},
"rust-analyzer.checkOnSave.command": "clippy",
"rust-analyzer.checkOnSave.allTargets": true,
"rust-analyzer.checkOnSave.extraArgs": ["--","-W","clippy::pedantic"],
"rust-analyzer.checkOnSave": true,
"rust-analyzer.check.allTargets": true,
"rust-analyzer.check.command": "clippy",
"rust-analyzer.check.extraArgs": ["--","-W","clippy::pedantic"],
}
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ uuid = { version = "1", features = ["v4"] }
axum = "0.6.1"
axum-server = { version = "0.4.4", features = ["tls-rustls"] }

derive_builder = "0.12"
derive-getters = "0.2"


[dev-dependencies]
mockall = "0.11"
Expand Down
1 change: 1 addition & 0 deletions cSpell.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
"trackerid",
"typenum",
"Unamed",
"unnested",
"untuple",
"uroot",
"Vagaa",
Expand Down
12 changes: 12 additions & 0 deletions src/apis/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use std::panic::Location;

use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
#[error("failed to parse settings {message}, {location}")]
ParseConfig {
location: &'static Location<'static>,
message: String,
},
}
2 changes: 2 additions & 0 deletions src/apis/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod error;
pub mod handlers;
pub mod middlewares;
pub mod resources;
pub mod responses;
pub mod routes;
pub mod server;
pub mod settings;
4 changes: 4 additions & 0 deletions src/apis/resources/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use std::collections::BTreeMap;

pub mod auth_key;
pub mod peer;
pub mod stats;
pub mod torrent;

pub type ApiTokens = BTreeMap<String, String>;
128 changes: 128 additions & 0 deletions src/apis/settings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use std::collections::BTreeMap;
use std::net::SocketAddr;
use std::panic::Location;
use std::path::Path;
use std::str::FromStr;

use derive_builder::Builder;
use derive_getters::Getters;
use serde::{Deserialize, Serialize};

use super::error::Error;
use crate::config::HttpApi;
use crate::errors::settings::ServiceSettingsError;
use crate::settings::{Service, ServiceProtocol};
use crate::tracker::services::common::{Tls, TlsBuilder};
use crate::{check_field_is_not_empty, check_field_is_not_none};

pub type ApiTokens = BTreeMap<String, String>;

#[derive(Builder, Getters, Default, Serialize, Deserialize, PartialEq, PartialOrd, Ord, Eq, Debug, Clone, Hash)]
#[builder(default, pattern = "immutable")]
pub struct Settings {
#[builder(setter(into), default = "\"default_api\".to_string()")]
#[getter(rename = "get_id")]
id: String,
#[builder(default = "false")]
#[getter(rename = "is_enabled")]
enabled: bool,
#[builder(setter(into), default = "\"HTTP API (default)\".to_string()")]
#[getter(rename = "get_display_name")]
display_name: String,
#[builder(default = "Some(SocketAddr::from_str(\"127.0.0.1:1212\").unwrap())")]
#[getter(rename = "get_socket")]
socket: Option<SocketAddr>,
#[builder(default = "self.api_token_default()")]
#[getter(rename = "get_access_tokens")]
access_tokens: ApiTokens,
#[getter(rename = "get_tls_settings")]
tls: Option<Tls>,
}

impl SettingsBuilder {
// Private helper method that will set the default database path if the database is Sqlite.
#[allow(clippy::unused_self)]
fn api_token_default(&self) -> ApiTokens {
let mut access_tokens = BTreeMap::new();
access_tokens.insert("admin".to_string(), "password".to_string());
access_tokens
}
}

impl TryFrom<&HttpApi> for Settings {
type Error = Error;

fn try_from(api: &HttpApi) -> Result<Self, Self::Error> {
let tls = if api.ssl_enabled {
let cert = Path::new(match &api.ssl_cert_path {
Some(p) => p,
None => {
return Err(Error::ParseConfig {
location: Location::caller(),
message: "ssl_cert_path is none and tls is enabled!".to_string(),
})
}
});

let key = Path::new(match &api.ssl_key_path {
Some(p) => p,
None => {
return Err(Error::ParseConfig {
location: Location::caller(),
message: "ssl_key_path is none and tls is enabled!".to_string(),
})
}
});

Some(
TlsBuilder::default()
.certificate_file_path(cert.into())
.key_file_path(key.into())
.build()
.expect("failed to build tls settings"),
)
} else {
None
};

Ok(SettingsBuilder::default()
.id("imported_api")
.enabled(api.enabled)
.display_name("Imported API")
.socket(SocketAddr::from_str(api.bind_address.as_str()).ok())
.access_tokens(api.access_tokens.clone())
.tls(tls)
.build()
.expect("failed to import settings"))
}
}

impl TryFrom<(&String, &Service)> for Settings {
type Error = ServiceSettingsError;

fn try_from(value: (&String, &Service)) -> Result<Self, Self::Error> {
check_field_is_not_none!(value.1 => ServiceSettingsError;
enabled, service);

if value.1.service.unwrap() != ServiceProtocol::Api {
return Err(ServiceSettingsError::WrongService {
field: "service".to_string(),
expected: ServiceProtocol::Api,
found: value.1.service.unwrap(),
data: value.1.into(),
});
}

check_field_is_not_empty!(value.1 => ServiceSettingsError;
display_name: String);

Ok(Self {
id: value.0.clone(),
enabled: value.1.enabled.unwrap(),
display_name: value.1.display_name.clone().unwrap(),
socket: Some(value.1.get_socket()?),
access_tokens: value.1.get_api_tokens()?,
tls: value.1.get_tls()?,
})
}
}
8 changes: 4 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, BTreeSet};
use std::net::IpAddr;
use std::panic::Location;
use std::path::Path;
Expand Down Expand Up @@ -46,14 +46,14 @@ pub struct HttpApi {
pub ssl_cert_path: Option<String>,
#[serde_as(as = "NoneAsEmptyString")]
pub ssl_key_path: Option<String>,
pub access_tokens: HashMap<String, String>,
pub access_tokens: BTreeMap<String, String>,
}

impl HttpApi {
#[must_use]
pub fn contains_token(&self, token: &str) -> bool {
let tokens: HashMap<String, String> = self.access_tokens.clone();
let tokens: HashSet<String> = tokens.into_values().collect();
let tokens: BTreeMap<String, String> = self.access_tokens.clone();
let tokens: BTreeSet<String> = tokens.into_values().collect();
tokens.contains(token)
}
}
Expand Down
21 changes: 16 additions & 5 deletions src/databases/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use serde::{Deserialize, Serialize};

use super::error::Error;
use super::mysql::Mysql;
use super::settings::Settings;
use super::sqlite::Sqlite;
use super::{Builder, Database};

#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, derive_more::Display, Clone)]
#[derive(Default, Serialize, Deserialize, Hash, PartialEq, PartialOrd, Ord, Eq, Copy, Debug, Clone)]
pub enum Driver {
#[default]
Sqlite3,
MySQL,
}
Expand All @@ -17,14 +19,23 @@ impl Driver {
/// # Errors
///
/// This function will return an error if unable to connect to the database.
pub fn build(&self, db_path: &str) -> Result<Box<dyn Database>, Error> {
let database = match self {
Driver::Sqlite3 => Builder::<Sqlite>::build(db_path),
Driver::MySQL => Builder::<Mysql>::build(db_path),
pub fn build(settings: &Settings) -> Result<Box<dyn Database>, Error> {
let database = match settings.driver {
Driver::Sqlite3 => Builder::<Sqlite>::build(settings),
Driver::MySQL => Builder::<Mysql>::build(settings),
}?;

database.create_database_tables().expect("Could not create database tables.");

Ok(database)
}
}

impl std::fmt::Display for Driver {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Sqlite3 => write!(f, "sqllite3"),
Self::MySQL => write!(f, "my_sql"),
}
}
}
15 changes: 15 additions & 0 deletions src/databases/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::sync::Arc;
use r2d2_mysql::mysql::UrlError;

use super::driver::Driver;
use super::settings::Settings;
use crate::located_error::{Located, LocatedError};

#[derive(thiserror::Error, Debug, Clone)]
Expand Down Expand Up @@ -44,6 +45,20 @@ pub enum Error {
source: LocatedError<'static, r2d2::Error>,
driver: Driver,
},

#[error("Failed to convert to driver settings, expected: {expected}, actual {actual}, settings: {settings:?}, {location}")]
WrongDriver {
location: &'static Location<'static>,
expected: Driver,
actual: Driver,
settings: Settings,
},

#[error("Failed to get required felid from settings: {felid}, {location}")]
MissingFelid {
location: &'static Location<'static>,
felid: String,
},
}

impl From<r2d2_sqlite::rusqlite::Error> for Error {
Expand Down
8 changes: 5 additions & 3 deletions src/databases/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
pub mod driver;
pub mod error;
pub mod mysql;
pub mod settings;
pub mod sqlite;

use std::marker::PhantomData;

use async_trait::async_trait;

use self::error::Error;
use self::settings::Settings;
use crate::protocol::info_hash::InfoHash;
use crate::tracker::auth;

Expand All @@ -27,8 +29,8 @@ where
/// # Errors
///
/// Will return `r2d2::Error` if `db_path` is not able to create a database.
pub(self) fn build(db_path: &str) -> Result<Box<dyn Database>, Error> {
Ok(Box::new(T::new(db_path)?))
pub(self) fn build(settings: &Settings) -> Result<Box<dyn Database>, Error> {
Ok(Box::new(T::new(settings)?))
}
}

Expand All @@ -39,7 +41,7 @@ pub trait Database: Sync + Send {
/// # Errors
///
/// Will return `r2d2::Error` if `db_path` is not able to create a database.
fn new(db_path: &str) -> Result<Self, Error>
fn new(settings: &Settings) -> Result<Self, Error>
where
Self: std::marker::Sized;

Expand Down
Loading