From 6c98e2b262f34a90ada7643c2e8f76c0e843dfb5 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 17 May 2024 15:52:24 +0100 Subject: [PATCH] refactor: [#589] versioning for configuration --- src/{config.rs => config/mod.rs} | 296 +------------------ src/config/v1/api.rs | 19 ++ src/config/v1/auth.rs | 48 +++ src/config/v1/database.rs | 16 + src/config/v1/image_cache.rs | 37 +++ src/config/v1/mail.rs | 34 +++ src/config/v1/mod.rs | 64 ++++ src/config/v1/net.rs | 25 ++ src/config/v1/tracker.rs | 38 +++ src/config/v1/tracker_statistics_importer.rs | 19 ++ src/config/v1/website.rs | 16 + 11 files changed, 330 insertions(+), 282 deletions(-) rename src/{config.rs => config/mod.rs} (66%) create mode 100644 src/config/v1/api.rs create mode 100644 src/config/v1/auth.rs create mode 100644 src/config/v1/database.rs create mode 100644 src/config/v1/image_cache.rs create mode 100644 src/config/v1/mail.rs create mode 100644 src/config/v1/mod.rs create mode 100644 src/config/v1/net.rs create mode 100644 src/config/v1/tracker.rs create mode 100644 src/config/v1/tracker_statistics_importer.rs create mode 100644 src/config/v1/website.rs diff --git a/src/config.rs b/src/config/mod.rs similarity index 66% rename from src/config.rs rename to src/config/mod.rs index e863250d..451b1315 100644 --- a/src/config.rs +++ b/src/config/mod.rs @@ -1,4 +1,6 @@ //! Configuration for the application. +pub mod v1; + use std::sync::Arc; use std::{env, fs}; @@ -11,6 +13,18 @@ use tokio::sync::RwLock; use torrust_index_located_error::{Located, LocatedError}; use url::{ParseError, Url}; +pub type TorrustIndex = v1::TorrustIndex; +pub type Api = v1::api::Api; +pub type Auth = v1::auth::Auth; +pub type Database = v1::database::Database; +pub type ImageCache = v1::image_cache::ImageCache; +pub type Mail = v1::mail::Mail; +pub type Network = v1::net::Network; +pub type TrackerStatisticsImporter = v1::tracker_statistics_importer::TrackerStatisticsImporter; +pub type Tracker = v1::tracker::Tracker; +pub type Website = v1::website::Website; +pub type EmailOnSignup = v1::auth::EmailOnSignup; + /// Information required for loading config #[derive(Debug, Default, Clone)] pub struct Info { @@ -120,21 +134,6 @@ impl From for Error { } } -/// Information displayed to the user in the website. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Website { - /// The name of the website. - pub name: String, -} - -impl Default for Website { - fn default() -> Self { - Self { - name: "Torrust".to_string(), - } - } -} - /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) /// crate for more information. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] @@ -168,235 +167,11 @@ impl TrackerMode { } } -/// Configuration for the associated tracker. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Tracker { - /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. - pub url: String, - /// The mode of the tracker. For example: `Public`. - /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) - /// crate for more information. - pub mode: TrackerMode, - /// The url of the tracker API. For example: `http://localhost:1212`. - pub api_url: String, - /// The token used to authenticate with the tracker API. - pub token: String, - /// The amount of seconds the token is valid. - pub token_valid_seconds: u64, -} - -impl Tracker { - fn override_tracker_api_token(&mut self, tracker_api_token: &str) { - self.token = tracker_api_token.to_string(); - } -} - -impl Default for Tracker { - fn default() -> Self { - Self { - url: "udp://localhost:6969".to_string(), - mode: TrackerMode::default(), - api_url: "http://localhost:1212".to_string(), - token: "MyAccessToken".to_string(), - token_valid_seconds: 7_257_600, - } - } -} - /// Port number representing that the OS will choose one randomly from the available ports. /// /// It's the port number `0` pub const FREE_PORT: u16 = 0; -/// The the base URL for the API. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Network { - /// The port to listen on. Default to `3001`. - pub port: u16, - /// The base URL for the API. For example: `http://localhost`. - /// If not set, the base URL will be inferred from the request. - pub base_url: Option, - /// TSL configuration. - pub tsl: Option, -} - -impl Default for Network { - fn default() -> Self { - Self { - port: 3001, - base_url: None, - tsl: None, - } - } -} - -/// Whether the email is required on signup or not. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum EmailOnSignup { - /// The email is required on signup. - Required, - /// The email is optional on signup. - Optional, - /// The email is not allowed on signup. It will only be ignored if provided. - None, // code-review: rename to `Ignored`? -} - -impl Default for EmailOnSignup { - fn default() -> Self { - Self::Optional - } -} - -/// Authentication options. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Auth { - /// Whether or not to require an email on signup. - pub email_on_signup: EmailOnSignup, - /// The minimum password length. - pub min_password_length: usize, - /// The maximum password length. - pub max_password_length: usize, - /// The secret key used to sign JWT tokens. - pub secret_key: String, -} - -impl Default for Auth { - fn default() -> Self { - Self { - email_on_signup: EmailOnSignup::default(), - min_password_length: 6, - max_password_length: 64, - secret_key: "MaxVerstappenWC2021".to_string(), - } - } -} - -impl Auth { - fn override_secret_key(&mut self, secret_key: &str) { - self.secret_key = secret_key.to_string(); - } -} - -/// Database configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Database { - /// The connection string for the database. For example: `sqlite://data.db?mode=rwc`. - pub connect_url: String, -} - -impl Default for Database { - fn default() -> Self { - Self { - connect_url: "sqlite://data.db?mode=rwc".to_string(), - } - } -} - -/// SMTP configuration. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Mail { - /// Whether or not to enable email verification on signup. - pub email_verification_enabled: bool, - /// The email address to send emails from. - pub from: String, - /// The email address to reply to. - pub reply_to: String, - /// The username to use for SMTP authentication. - pub username: String, - /// The password to use for SMTP authentication. - pub password: String, - /// The SMTP server to use. - pub server: String, - /// The SMTP port to use. - pub port: u16, -} - -impl Default for Mail { - fn default() -> Self { - Self { - email_verification_enabled: false, - from: "example@email.com".to_string(), - reply_to: "noreply@email.com".to_string(), - username: String::default(), - password: String::default(), - server: String::default(), - port: 25, - } - } -} - -/// Configuration for the image proxy cache. -/// -/// Users have a cache quota per period. For example: 100MB per day. -/// When users are navigating the site, they will be downloading images that are -/// embedded in the torrent description. These images will be cached in the -/// proxy. The proxy will not download new images if the user has reached the -/// quota. -#[allow(clippy::module_name_repetitions)] -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct ImageCache { - /// Maximum time in seconds to wait for downloading the image form the original source. - pub max_request_timeout_ms: u64, - /// Cache size in bytes. - pub capacity: usize, - /// Maximum size in bytes for a single image. - pub entry_size_limit: usize, - /// Users have a cache quota per period. For example: 100MB per day. - /// This is the period in seconds (1 day in seconds). - pub user_quota_period_seconds: u64, - /// Users have a cache quota per period. For example: 100MB per day. - /// This is the maximum size in bytes (100MB in bytes). - pub user_quota_bytes: usize, -} - -/// Core configuration for the API -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Api { - /// The default page size for torrent lists. - pub default_torrent_page_size: u8, - /// The maximum page size for torrent lists. - pub max_torrent_page_size: u8, -} - -impl Default for Api { - fn default() -> Self { - Self { - default_torrent_page_size: 10, - max_torrent_page_size: 30, - } - } -} - -/// Configuration for the tracker statistics importer. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct TrackerStatisticsImporter { - /// The interval in seconds to get statistics from the tracker. - pub torrent_info_update_interval: u64, - /// The port the Importer API is listening on. Default to `3002`. - pub port: u16, -} - -impl Default for TrackerStatisticsImporter { - fn default() -> Self { - Self { - torrent_info_update_interval: 3600, - port: 3002, - } - } -} - -impl Default for ImageCache { - fn default() -> Self { - Self { - max_request_timeout_ms: 1000, - capacity: 128_000_000, - entry_size_limit: 4_000_000, - user_quota_period_seconds: 3600, - user_quota_bytes: 64_000_000, - } - } -} - #[serde_as] #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, Default)] pub struct Tsl { @@ -422,49 +197,6 @@ impl Tsl { } } -/// The whole configuration for the index. -#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] -pub struct TorrustIndex { - /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, - /// `Debug` and `Trace`. Default is `Info`. - pub log_level: Option, - /// The website customizable values. - pub website: Website, - /// The tracker configuration. - pub tracker: Tracker, - /// The network configuration. - pub net: Network, - /// The authentication configuration. - pub auth: Auth, - /// The database configuration. - pub database: Database, - /// The SMTP configuration. - pub mail: Mail, - /// The image proxy cache configuration. - pub image_cache: ImageCache, - /// The API configuration. - pub api: Api, - /// The tracker statistics importer job configuration. - pub tracker_statistics_importer: TrackerStatisticsImporter, -} - -impl TorrustIndex { - fn override_tracker_api_token(&mut self, tracker_api_token: &str) { - self.tracker.override_tracker_api_token(tracker_api_token); - } - - fn override_auth_secret_key(&mut self, auth_secret_key: &str) { - self.auth.override_secret_key(auth_secret_key); - } - - pub fn remove_secrets(&mut self) { - "***".clone_into(&mut self.tracker.token); - "***".clone_into(&mut self.database.connect_url); - "***".clone_into(&mut self.mail.password); - "***".clone_into(&mut self.auth.secret_key); - } -} - /// The configuration service. #[derive(Debug)] pub struct Configuration { diff --git a/src/config/v1/api.rs b/src/config/v1/api.rs new file mode 100644 index 00000000..70f3ddab --- /dev/null +++ b/src/config/v1/api.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +/// Core configuration for the API +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Api { + /// The default page size for torrent lists. + pub default_torrent_page_size: u8, + /// The maximum page size for torrent lists. + pub max_torrent_page_size: u8, +} + +impl Default for Api { + fn default() -> Self { + Self { + default_torrent_page_size: 10, + max_torrent_page_size: 30, + } + } +} diff --git a/src/config/v1/auth.rs b/src/config/v1/auth.rs new file mode 100644 index 00000000..13fec842 --- /dev/null +++ b/src/config/v1/auth.rs @@ -0,0 +1,48 @@ +use serde::{Deserialize, Serialize}; + +/// Authentication options. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Auth { + /// Whether or not to require an email on signup. + pub email_on_signup: EmailOnSignup, + /// The minimum password length. + pub min_password_length: usize, + /// The maximum password length. + pub max_password_length: usize, + /// The secret key used to sign JWT tokens. + pub secret_key: String, +} + +impl Default for Auth { + fn default() -> Self { + Self { + email_on_signup: EmailOnSignup::default(), + min_password_length: 6, + max_password_length: 64, + secret_key: "MaxVerstappenWC2021".to_string(), + } + } +} + +impl Auth { + pub fn override_secret_key(&mut self, secret_key: &str) { + self.secret_key = secret_key.to_string(); + } +} + +/// Whether the email is required on signup or not. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub enum EmailOnSignup { + /// The email is required on signup. + Required, + /// The email is optional on signup. + Optional, + /// The email is not allowed on signup. It will only be ignored if provided. + None, // code-review: rename to `Ignored`? +} + +impl Default for EmailOnSignup { + fn default() -> Self { + Self::Optional + } +} diff --git a/src/config/v1/database.rs b/src/config/v1/database.rs new file mode 100644 index 00000000..325a8936 --- /dev/null +++ b/src/config/v1/database.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +/// Database configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Database { + /// The connection string for the database. For example: `sqlite://data.db?mode=rwc`. + pub connect_url: String, +} + +impl Default for Database { + fn default() -> Self { + Self { + connect_url: "sqlite://data.db?mode=rwc".to_string(), + } + } +} diff --git a/src/config/v1/image_cache.rs b/src/config/v1/image_cache.rs new file mode 100644 index 00000000..4f2990b0 --- /dev/null +++ b/src/config/v1/image_cache.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; + +/// Configuration for the image proxy cache. +/// +/// Users have a cache quota per period. For example: 100MB per day. +/// When users are navigating the site, they will be downloading images that are +/// embedded in the torrent description. These images will be cached in the +/// proxy. The proxy will not download new images if the user has reached the +/// quota. +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ImageCache { + /// Maximum time in seconds to wait for downloading the image form the original source. + pub max_request_timeout_ms: u64, + /// Cache size in bytes. + pub capacity: usize, + /// Maximum size in bytes for a single image. + pub entry_size_limit: usize, + /// Users have a cache quota per period. For example: 100MB per day. + /// This is the period in seconds (1 day in seconds). + pub user_quota_period_seconds: u64, + /// Users have a cache quota per period. For example: 100MB per day. + /// This is the maximum size in bytes (100MB in bytes). + pub user_quota_bytes: usize, +} + +impl Default for ImageCache { + fn default() -> Self { + Self { + max_request_timeout_ms: 1000, + capacity: 128_000_000, + entry_size_limit: 4_000_000, + user_quota_period_seconds: 3600, + user_quota_bytes: 64_000_000, + } + } +} diff --git a/src/config/v1/mail.rs b/src/config/v1/mail.rs new file mode 100644 index 00000000..4e166281 --- /dev/null +++ b/src/config/v1/mail.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; + +/// SMTP configuration. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Mail { + /// Whether or not to enable email verification on signup. + pub email_verification_enabled: bool, + /// The email address to send emails from. + pub from: String, + /// The email address to reply to. + pub reply_to: String, + /// The username to use for SMTP authentication. + pub username: String, + /// The password to use for SMTP authentication. + pub password: String, + /// The SMTP server to use. + pub server: String, + /// The SMTP port to use. + pub port: u16, +} + +impl Default for Mail { + fn default() -> Self { + Self { + email_verification_enabled: false, + from: "example@email.com".to_string(), + reply_to: "noreply@email.com".to_string(), + username: String::default(), + password: String::default(), + server: String::default(), + port: 25, + } + } +} diff --git a/src/config/v1/mod.rs b/src/config/v1/mod.rs new file mode 100644 index 00000000..fa44aafc --- /dev/null +++ b/src/config/v1/mod.rs @@ -0,0 +1,64 @@ +pub mod api; +pub mod auth; +pub mod database; +pub mod image_cache; +pub mod mail; +pub mod net; +pub mod tracker; +pub mod tracker_statistics_importer; +pub mod website; + +use serde::{Deserialize, Serialize}; + +use self::api::Api; +use self::auth::Auth; +use self::database::Database; +use self::image_cache::ImageCache; +use self::mail::Mail; +use self::net::Network; +use self::tracker::Tracker; +use self::tracker_statistics_importer::TrackerStatisticsImporter; +use self::website::Website; + +/// The whole configuration for the index. +#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)] +pub struct TorrustIndex { + /// Logging level. Possible values are: `Off`, `Error`, `Warn`, `Info`, + /// `Debug` and `Trace`. Default is `Info`. + pub log_level: Option, + /// The website customizable values. + pub website: Website, + /// The tracker configuration. + pub tracker: Tracker, + /// The network configuration. + pub net: Network, + /// The authentication configuration. + pub auth: Auth, + /// The database configuration. + pub database: Database, + /// The SMTP configuration. + pub mail: Mail, + /// The image proxy cache configuration. + pub image_cache: ImageCache, + /// The API configuration. + pub api: Api, + /// The tracker statistics importer job configuration. + pub tracker_statistics_importer: TrackerStatisticsImporter, +} + +impl TorrustIndex { + pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) { + self.tracker.override_tracker_api_token(tracker_api_token); + } + + pub fn override_auth_secret_key(&mut self, auth_secret_key: &str) { + self.auth.override_secret_key(auth_secret_key); + } + + pub fn remove_secrets(&mut self) { + "***".clone_into(&mut self.tracker.token); + "***".clone_into(&mut self.database.connect_url); + "***".clone_into(&mut self.mail.password); + "***".clone_into(&mut self.auth.secret_key); + } +} diff --git a/src/config/v1/net.rs b/src/config/v1/net.rs new file mode 100644 index 00000000..fa473e16 --- /dev/null +++ b/src/config/v1/net.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::Tsl; + +/// The the base URL for the API. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Network { + /// The port to listen on. Default to `3001`. + pub port: u16, + /// The base URL for the API. For example: `http://localhost`. + /// If not set, the base URL will be inferred from the request. + pub base_url: Option, + /// TSL configuration. + pub tsl: Option, +} + +impl Default for Network { + fn default() -> Self { + Self { + port: 3001, + base_url: None, + tsl: None, + } + } +} diff --git a/src/config/v1/tracker.rs b/src/config/v1/tracker.rs new file mode 100644 index 00000000..ff0ffe71 --- /dev/null +++ b/src/config/v1/tracker.rs @@ -0,0 +1,38 @@ +use serde::{Deserialize, Serialize}; + +use crate::config::TrackerMode; + +/// Configuration for the associated tracker. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Tracker { + /// Connection string for the tracker. For example: `udp://TRACKER_IP:6969`. + pub url: String, + /// The mode of the tracker. For example: `Public`. + /// See `TrackerMode` in [`torrust-tracker-primitives`](https://docs.rs/torrust-tracker-primitives) + /// crate for more information. + pub mode: TrackerMode, + /// The url of the tracker API. For example: `http://localhost:1212`. + pub api_url: String, + /// The token used to authenticate with the tracker API. + pub token: String, + /// The amount of seconds the token is valid. + pub token_valid_seconds: u64, +} + +impl Tracker { + pub fn override_tracker_api_token(&mut self, tracker_api_token: &str) { + self.token = tracker_api_token.to_string(); + } +} + +impl Default for Tracker { + fn default() -> Self { + Self { + url: "udp://localhost:6969".to_string(), + mode: TrackerMode::default(), + api_url: "http://localhost:1212".to_string(), + token: "MyAccessToken".to_string(), + token_valid_seconds: 7_257_600, + } + } +} diff --git a/src/config/v1/tracker_statistics_importer.rs b/src/config/v1/tracker_statistics_importer.rs new file mode 100644 index 00000000..9c43a4a4 --- /dev/null +++ b/src/config/v1/tracker_statistics_importer.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +/// Configuration for the tracker statistics importer. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct TrackerStatisticsImporter { + /// The interval in seconds to get statistics from the tracker. + pub torrent_info_update_interval: u64, + /// The port the Importer API is listening on. Default to `3002`. + pub port: u16, +} + +impl Default for TrackerStatisticsImporter { + fn default() -> Self { + Self { + torrent_info_update_interval: 3600, + port: 3002, + } + } +} diff --git a/src/config/v1/website.rs b/src/config/v1/website.rs new file mode 100644 index 00000000..a7a6fbef --- /dev/null +++ b/src/config/v1/website.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +/// Information displayed to the user in the website. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct Website { + /// The name of the website. + pub name: String, +} + +impl Default for Website { + fn default() -> Self { + Self { + name: "Torrust".to_string(), + } + } +}