From b4411ae892cad0f1ac6c976bf3ff6b92ecca6118 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Sat, 12 Apr 2025 15:58:19 +0200 Subject: [PATCH] Directories for database file, backups and logs are defined in configuration file now --- backend/src/config.rs | 32 +++++++++++++++++---- backend/src/consts.rs | 4 --- backend/src/data/backup.rs | 4 +++ backend/src/data/db/mod.rs | 7 +++-- backend/src/log.rs | 56 ++++++++++++++++++++++++++++++++++++ backend/src/main.rs | 59 +++++++++++++++++--------------------- 6 files changed, 118 insertions(+), 44 deletions(-) create mode 100644 backend/src/log.rs diff --git a/backend/src/config.rs b/backend/src/config.rs index df5977d..bfa18f5 100644 --- a/backend/src/config.rs +++ b/backend/src/config.rs @@ -26,11 +26,19 @@ pub struct Config { #[serde(default = "smtp_password_default")] pub smtp_password: String, - #[serde(default)] + #[serde(default = "backup_time_default")] pub backup_time: Option, // If not set, no backup will be done. - #[serde(default = "backup_directory_default")] - pub backup_directory: String, + /// Directory where the database is stored. + /// It's located in the current directory. + #[serde(default = "database_directory_default")] + pub database_directory: String, + + #[serde(default = "backups_directory_default")] + pub backups_directory: String, + + #[serde(default = "logs_directory_default")] + pub logs_directory: String, } fn port_default() -> u16 { @@ -49,10 +57,22 @@ fn smtp_password_default() -> String { "password".to_string() } -fn backup_directory_default() -> String { +fn backup_time_default() -> Option { + NaiveTime::from_hms_opt(4, 0, 0) // 4 am. +} + +fn database_directory_default() -> String { "data".to_string() } +fn backups_directory_default() -> String { + "data/backups".to_string() +} + +fn logs_directory_default() -> String { + "data/logs".to_string() +} + impl Config { pub fn default() -> Self { from_str("()").unwrap() @@ -68,7 +88,9 @@ impl fmt::Debug for Config { .field("smtp_login", &self.smtp_login) .field("smtp_password", &"*****") .field("backup_time", &self.backup_time) - .field("backup_directory", &self.backup_directory) + .field("database_directory", &self.database_directory) + .field("backups_directory", &self.backups_directory) + .field("logs_directory", &self.logs_directory) .finish() } } diff --git a/backend/src/consts.rs b/backend/src/consts.rs index 33a11a5..1e74425 100644 --- a/backend/src/consts.rs +++ b/backend/src/consts.rs @@ -11,10 +11,6 @@ pub const FILE_CONF: &str = "conf.ron"; /// it's located in the current directory. pub const TRANSLATION_FILE: &str = "translation.ron"; -/// Directory where the database is stored. -/// It's located in the current directory. -pub const DB_DIRECTORY: &str = "data"; - /// Filename of the database. /// It's located in the `DB_DIRECTORY` directory. pub const DB_FILENAME: &str = "recipes.sqlite"; diff --git a/backend/src/data/backup.rs b/backend/src/data/backup.rs index ded9aa2..f561e77 100644 --- a/backend/src/data/backup.rs +++ b/backend/src/data/backup.rs @@ -16,6 +16,10 @@ pub fn start

( where P: AsRef + Send + Sync + 'static, { + if !directory.as_ref().exists() { + std::fs::DirBuilder::new().create(&directory).unwrap(); + } + if !directory.as_ref().is_dir() { panic!( "Path must be a directory: {}", diff --git a/backend/src/data/db/mod.rs b/backend/src/data/db/mod.rs index c321273..0e19a8b 100644 --- a/backend/src/data/db/mod.rs +++ b/backend/src/data/db/mod.rs @@ -51,8 +51,11 @@ pub struct Connection { } impl Connection { - pub async fn new() -> Result { - let path = Path::new(consts::DB_DIRECTORY).join(consts::DB_FILENAME); + pub async fn new

(directory: P) -> Result + where + P: AsRef, + { + let path = directory.as_ref().join(consts::DB_FILENAME); Self::new_from_file(path).await } diff --git a/backend/src/log.rs b/backend/src/log.rs new file mode 100644 index 0000000..e941a13 --- /dev/null +++ b/backend/src/log.rs @@ -0,0 +1,56 @@ +use std::{fs, path::Path}; + +use tracing_subscriber::{ + fmt::writer::MakeWriterExt, layer::SubscriberExt, util::SubscriberInitExt, +}; + +#[cfg(debug_assertions)] +const TRACING_LEVEL: tracing::Level = tracing::Level::DEBUG; + +#[cfg(debug_assertions)] +const TRACING_DISPLAY_THREAD: bool = true; + +#[cfg(not(debug_assertions))] +const TRACING_LEVEL: tracing::Level = tracing::Level::INFO; + +#[cfg(not(debug_assertions))] +const TRACING_DISPLAY_THREAD: bool = false; + +pub fn init

(directory: P) +where + P: AsRef, +{ + if !directory.as_ref().exists() { + fs::DirBuilder::new().create(&directory).unwrap(); + } + + let log_filepath = directory.as_ref().join(format!( + "recipes_{}.log", + chrono::Local::now().format("%Y-%m-%d_%H%M%S") + )); + + println!("log file: {}", log_filepath.to_str().unwrap_or_default()); + + match std::fs::File::create(log_filepath) { + Ok(log_file) => { + let layer_file = tracing_subscriber::fmt::layer() + .with_writer(log_file.with_max_level(TRACING_LEVEL)) + .with_ansi(false) + .with_thread_ids(TRACING_DISPLAY_THREAD) + .with_thread_names(TRACING_DISPLAY_THREAD); + + let layer_stdout = tracing_subscriber::fmt::layer() + .with_writer(std::io::stdout.with_max_level(TRACING_LEVEL)) + .with_thread_ids(TRACING_DISPLAY_THREAD) + .with_thread_names(TRACING_DISPLAY_THREAD); + + tracing_subscriber::Registry::default() + .with(layer_file) + .with(layer_stdout) + .init(); + } + Err(error) => { + println!("Unable to open log file: {}", error); + } + } +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 3462b8f..41687ef 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -34,6 +34,7 @@ mod data; mod email; mod hash; mod html_templates; +mod log; mod ron_extractor; mod ron_utils; mod services; @@ -81,18 +82,6 @@ impl axum::response::IntoResponse for AppError { } } -#[cfg(debug_assertions)] -const TRACING_LEVEL: tracing::Level = tracing::Level::DEBUG; - -#[cfg(debug_assertions)] -const TRACING_DISPLAY_THREAD: bool = true; - -#[cfg(not(debug_assertions))] -const TRACING_LEVEL: tracing::Level = tracing::Level::INFO; - -#[cfg(not(debug_assertions))] -const TRACING_DISPLAY_THREAD: bool = false; - #[derive(Debug, Clone)] struct Context { user: Option, @@ -100,37 +89,38 @@ struct Context { dark_theme: bool, } +use tracing_subscriber::{Registry, fmt::layer, layer::SubscriberExt, util::SubscriberInitExt}; + // TODO: Should main returns 'Result'? #[tokio::main] async fn main() { - tracing_subscriber::fmt() - .with_max_level(TRACING_LEVEL) - .with_thread_ids(TRACING_DISPLAY_THREAD) - .with_thread_names(TRACING_DISPLAY_THREAD) - .init(); + let config = config::load(); + log::init(&config.logs_directory); - if !process_args().await { + event!(Level::INFO, "Configuration: {:?}", config); + + if !process_args(&config.database_directory).await { return; } event!(Level::INFO, "Starting Recipes as web server..."); - let config = config::load(); - let port = config.port; - - event!(Level::INFO, "Configuration: {:?}", config); - - let Ok(db_connection) = db::Connection::new().await else { + let Ok(db_connection) = db::Connection::new(&config.database_directory).await else { event!(Level::ERROR, "Unable to connect to the database"); return; }; - backup::start( - "data", - db_connection.clone(), - // TODO: take from config. - NaiveTime::from_hms_opt(4, 0, 0).expect("Invalid time of day"), - ); + if let Some(backup_time) = config.backup_time { + backup::start( + config.backups_directory.clone(), + db_connection.clone(), + backup_time, + ); + } else { + event!(Level::INFO, "Backups disabled by config"); + } + + let port = config.port; let state = AppState { config, @@ -474,12 +464,15 @@ struct Args { } /// Returns `true` if the server can be started. -async fn process_args() -> bool { +async fn process_args

(database_directory: P) -> bool +where + P: AsRef, +{ let args = Args::parse(); if args.dbtest { // Make a backup of the database. - let db_path = Path::new(consts::DB_DIRECTORY).join(consts::DB_FILENAME); + let db_path = database_directory.as_ref().join(consts::DB_FILENAME); if db_path.exists() { let db_path_bckup = (1..) .find_map(|n| { @@ -498,7 +491,7 @@ async fn process_args() -> bool { }); } - match db::Connection::new().await { + match db::Connection::new(database_directory).await { Ok(con) => { if let Err(error) = con.execute_file("sql/data_test.sql").await { event!(Level::ERROR, "{}", error);