Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration tests scaffolding #129

Merged
merged 3 commits into from
Apr 29, 2023
Merged
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
2 changes: 2 additions & 0 deletions project-words.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
actix
addrs
AUTOINCREMENT
bencode
bencoded
Expand Down Expand Up @@ -31,6 +32,7 @@ NCCA
nilm
nocapture
Oberhachingerstr
oneshot
ppassword
reqwest
Roadmap
Expand Down
107 changes: 107 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use std::net::SocketAddr;
use std::sync::Arc;

use actix_cors::Cors;
use actix_web::dev::Server;
use actix_web::{middleware, web, App, HttpServer};
use log::info;

use crate::auth::AuthorizationService;
use crate::bootstrap::logging;
use crate::common::AppData;
use crate::config::Configuration;
use crate::databases::database::connect_database;
use crate::mailer::MailerService;
use crate::routes;
use crate::tracker::TrackerService;

pub struct Running {
pub api_server: Server,
pub socket_address: SocketAddr,
pub tracker_data_importer_handle: tokio::task::JoinHandle<()>,
}

pub async fn run(configuration: Configuration) -> Running {
logging::setup();

let cfg = Arc::new(configuration);

// Get configuration settings needed to build the app dependencies and
// services: main API server and tracker torrents importer.

let settings = cfg.settings.read().await;

let database_connect_url = settings.database.connect_url.clone();
let database_torrent_info_update_interval = settings.database.torrent_info_update_interval;
let net_port = settings.net.port;

// IMPORTANT: drop settings before starting server to avoid read locks that
// leads to requests hanging.
drop(settings);

// Build app dependencies

let database = Arc::new(connect_database(&database_connect_url).await.expect("Database error."));
let auth = Arc::new(AuthorizationService::new(cfg.clone(), database.clone()));
let tracker_service = Arc::new(TrackerService::new(cfg.clone(), database.clone()));
let mailer_service = Arc::new(MailerService::new(cfg.clone()).await);

// Build app container

let app_data = Arc::new(AppData::new(
cfg.clone(),
database.clone(),
auth.clone(),
tracker_service.clone(),
mailer_service,
));

// Start repeating task to import tracker torrent data and updating
// seeders and leechers info.

let weak_tracker_service = Arc::downgrade(&tracker_service);

let tracker_data_importer_handle = tokio::spawn(async move {
let interval = std::time::Duration::from_secs(database_torrent_info_update_interval);
let mut interval = tokio::time::interval(interval);
interval.tick().await; // first tick is immediate...
loop {
interval.tick().await;
if let Some(tracker) = weak_tracker_service.upgrade() {
let _ = tracker.update_torrents().await;
} else {
break;
}
}
});

// Start main API server

// todo: get IP from settings
let ip = "0.0.0.0".to_string();

let server = HttpServer::new(move || {
App::new()
.wrap(Cors::permissive())
.app_data(web::Data::new(app_data.clone()))
.wrap(middleware::Logger::default())
.configure(routes::init_routes)
})
.bind((ip, net_port))
.expect("can't bind server to socket address");

let socket_address = server.addrs()[0];

let running_server = server.run();

let starting_message = format!("Listening on http://{}", socket_address);
info!("{}", starting_message);
// Logging could be disabled or redirected to file. So print to stdout too.
println!("{}", starting_message);

Running {
api_server: running_server,
socket_address,
tracker_data_importer_handle,
}
}
92 changes: 5 additions & 87 deletions src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,93 +1,11 @@
use std::env;
use std::sync::Arc;

use actix_cors::Cors;
use actix_web::{middleware, web, App, HttpServer};
use torrust_index_backend::auth::AuthorizationService;
use torrust_index_backend::bootstrap::logging;
use torrust_index_backend::common::AppData;
use torrust_index_backend::config::{Configuration, CONFIG_ENV_VAR_NAME, CONFIG_PATH};
use torrust_index_backend::databases::database::connect_database;
use torrust_index_backend::mailer::MailerService;
use torrust_index_backend::routes;
use torrust_index_backend::tracker::TrackerService;
use torrust_index_backend::app;
use torrust_index_backend::bootstrap::config::init_configuration;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
async fn main() -> Result<(), std::io::Error> {
let configuration = init_configuration().await;

logging::setup();

let cfg = Arc::new(configuration);

let settings = cfg.settings.read().await;

let database = Arc::new(
connect_database(&settings.database.connect_url)
.await
.expect("Database error."),
);

let auth = Arc::new(AuthorizationService::new(cfg.clone(), database.clone()));
let tracker_service = Arc::new(TrackerService::new(cfg.clone(), database.clone()));
let mailer_service = Arc::new(MailerService::new(cfg.clone()).await);
let app_data = Arc::new(AppData::new(
cfg.clone(),
database.clone(),
auth.clone(),
tracker_service.clone(),
mailer_service.clone(),
));

let interval = settings.database.torrent_info_update_interval;
let weak_tracker_service = Arc::downgrade(&tracker_service);

// repeating task, update all seeders and leechers info
tokio::spawn(async move {
let interval = std::time::Duration::from_secs(interval);
let mut interval = tokio::time::interval(interval);
interval.tick().await; // first tick is immediate...
loop {
interval.tick().await;
if let Some(tracker) = weak_tracker_service.upgrade() {
let _ = tracker.update_torrents().await;
} else {
break;
}
}
});

let port = settings.net.port;

drop(settings);

println!("Listening on http://0.0.0.0:{}", port);

HttpServer::new(move || {
App::new()
.wrap(Cors::permissive())
.app_data(web::Data::new(app_data.clone()))
.wrap(middleware::Logger::default())
.configure(routes::init_routes)
})
.bind(("0.0.0.0", port))?
.run()
.await
}

async fn init_configuration() -> Configuration {
if env::var(CONFIG_ENV_VAR_NAME).is_ok() {
println!("Loading configuration from env var `{}`", CONFIG_ENV_VAR_NAME);

Configuration::load_from_env_var(CONFIG_ENV_VAR_NAME).unwrap()
} else {
println!("Loading configuration from config file `{}`", CONFIG_PATH);
let app = app::run(configuration).await;

match Configuration::load_from_file().await {
Ok(config) => config,
Err(error) => {
panic!("{}", error)
}
}
}
app.api_server.await
}
28 changes: 28 additions & 0 deletions src/bootstrap/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use std::env;

pub const CONFIG_PATH: &str = "./config.toml";
pub const CONFIG_ENV_VAR_NAME: &str = "TORRUST_IDX_BACK_CONFIG";

use crate::config::Configuration;

/// Initialize configuration from file or env var.
///
/// # Panics
///
/// Will panic if configuration is not found or cannot be parsed
pub async fn init_configuration() -> Configuration {
if env::var(CONFIG_ENV_VAR_NAME).is_ok() {
println!("Loading configuration from env var `{}`", CONFIG_ENV_VAR_NAME);

Configuration::load_from_env_var(CONFIG_ENV_VAR_NAME).unwrap()
} else {
println!("Loading configuration from config file `{}`", CONFIG_PATH);

match Configuration::load_from_file(CONFIG_PATH).await {
Ok(config) => config,
Err(error) => {
panic!("{}", error)
}
}
}
}
1 change: 1 addition & 0 deletions src/bootstrap/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod config;
pub mod logging;
48 changes: 26 additions & 22 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ use log::warn;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;

pub const CONFIG_PATH: &str = "./config.toml";
pub const CONFIG_ENV_VAR_NAME: &str = "TORRUST_IDX_BACK_CONFIG";

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Website {
pub name: String,
Expand All @@ -32,6 +29,9 @@ pub struct Tracker {
pub token_valid_seconds: u64,
}

/// Port 0 means that the OS will choose a random free port.
pub const FREE_PORT: u16 = 0;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Network {
pub port: u16,
Expand Down Expand Up @@ -80,14 +80,9 @@ pub struct TorrustConfig {
pub mail: Mail,
}

#[derive(Debug)]
pub struct Configuration {
pub settings: RwLock<TorrustConfig>,
}

impl Configuration {
pub fn default() -> Configuration {
let torrust_config = TorrustConfig {
impl TorrustConfig {
pub fn default() -> Self {
Self {
website: Website {
name: "Torrust".to_string(),
},
Expand All @@ -96,7 +91,7 @@ impl Configuration {
mode: TrackerMode::Public,
api_url: "http://localhost:1212".to_string(),
token: "MyAccessToken".to_string(),
token_valid_seconds: 7257600,
token_valid_seconds: 7_257_600,
},
net: Network {
port: 3000,
Expand All @@ -121,27 +116,36 @@ impl Configuration {
server: "".to_string(),
port: 25,
},
};
}
}
}

#[derive(Debug)]
pub struct Configuration {
pub settings: RwLock<TorrustConfig>,
}

impl Configuration {
pub fn default() -> Configuration {
Configuration {
settings: RwLock::new(torrust_config),
settings: RwLock::new(TorrustConfig::default()),
}
}

/// Loads the configuration from the configuration file.
pub async fn load_from_file() -> Result<Configuration, ConfigError> {
pub async fn load_from_file(config_path: &str) -> Result<Configuration, ConfigError> {
let config_builder = Config::builder();

#[allow(unused_assignments)]
let mut config = Config::default();

if Path::new(CONFIG_PATH).exists() {
config = config_builder.add_source(File::with_name(CONFIG_PATH)).build()?;
if Path::new(config_path).exists() {
config = config_builder.add_source(File::with_name(config_path)).build()?;
} else {
warn!("No config file found.");
warn!("Creating config file..");
let config = Configuration::default();
let _ = config.save_to_file().await;
let _ = config.save_to_file(config_path).await;
return Err(ConfigError::Message(
"Please edit the config.TOML in the root folder and restart the tracker.".to_string(),
));
Expand Down Expand Up @@ -183,24 +187,24 @@ impl Configuration {
}
}

pub async fn save_to_file(&self) -> Result<(), ()> {
pub async fn save_to_file(&self, config_path: &str) -> Result<(), ()> {
let settings = self.settings.read().await;

let toml_string = toml::to_string(&*settings).expect("Could not encode TOML value");

drop(settings);

fs::write(CONFIG_PATH, toml_string).expect("Could not write to file!");
fs::write(config_path, toml_string).expect("Could not write to file!");
Ok(())
}

pub async fn update_settings(&self, new_settings: TorrustConfig) -> Result<(), ()> {
pub async fn update_settings(&self, new_settings: TorrustConfig, config_path: &str) -> Result<(), ()> {
let mut settings = self.settings.write().await;
*settings = new_settings;

drop(settings);

let _ = self.save_to_file().await;
let _ = self.save_to_file(config_path).await;

Ok(())
}
Expand Down
14 changes: 7 additions & 7 deletions src/console/commands/import_tracker_statistics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ use std::sync::Arc;
use derive_more::{Display, Error};
use text_colorizer::*;

use crate::config::Configuration;
use crate::bootstrap::config::init_configuration;
use crate::bootstrap::logging;
use crate::databases::database::connect_database;
use crate::tracker::TrackerService;

Expand Down Expand Up @@ -57,12 +58,11 @@ pub async fn run_importer() {
pub async fn import(_args: &Arguments) {
println!("Importing statistics from linked tracker ...");

let cfg = match Configuration::load_from_file().await {
Ok(config) => Arc::new(config),
Err(error) => {
panic!("{}", error)
}
};
let configuration = init_configuration().await;

logging::setup();

let cfg = Arc::new(configuration);

let settings = cfg.settings.read().await;

Expand Down
Loading