Use data directory and add config defaults

This commit is contained in:
Mo 2023-02-26 14:51:57 +01:00
parent 4adc01c49c
commit 9f195fffce
4 changed files with 80 additions and 30 deletions

View file

@ -14,7 +14,7 @@ RUN cargo build --release --bin contact-form
FROM docker.io/library/debian:stable-slim AS runtime FROM docker.io/library/debian:stable-slim AS runtime
WORKDIR app WORKDIR app
ENV CF_CONFIG_FILE=/volumes/data/config.yaml ENV CF_DATA_DIR=/volumes/data
COPY --from=builder /app/target/release/contact-form /usr/local/bin/contact-form COPY --from=builder /app/target/release/contact-form /usr/local/bin/contact-form
COPY --from=builder /app/static/ ./static/ COPY --from=builder /app/static/ ./static/
CMD ["contact-form"] CMD ["contact-form"]

View file

@ -1,6 +1,6 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use serde::Deserialize; use serde::Deserialize;
use std::{env, fs::File, io::BufReader}; use std::{fs::File, io::BufReader, path::Path};
/// Email server credentials. /// Email server credentials.
#[derive(Deserialize)] #[derive(Deserialize)]
@ -20,7 +20,9 @@ pub struct Email {
/// UTC offset for time formatting. /// UTC offset for time formatting.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct UtcOffset { pub struct UtcOffset {
#[serde(default)]
pub hours: i8, pub hours: i8,
#[serde(default)]
pub minutes: i8, pub minutes: i8,
} }
@ -40,10 +42,18 @@ pub struct CustomField {
pub field_type: CustomFieldType, pub field_type: CustomFieldType,
} }
fn default_captcha_error() -> String {
"You did enter the wrong code at the end of the form. Please try again.".to_string()
}
fn default_email_error() -> String {
"An internal error occurred while sending your request. Please try again later.".to_string()
}
/// Error messages for localization. /// Error messages for localization.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ErrorMessages { pub struct ErrorMessages {
#[serde(default = "default_captcha_error")]
pub captcha_error: String, pub captcha_error: String,
#[serde(default = "default_email_error")]
pub email_error: String, pub email_error: String,
} }
@ -79,7 +89,7 @@ fn default_captcha_label() -> String {
"Enter the code above".to_string() "Enter the code above".to_string()
} }
fn default_captcha_required_feedback() -> String { fn default_captcha_required_feedback() -> String {
"Please enter code from the image above".to_string() "Please enter the code from the image above".to_string()
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CaptchaField { pub struct CaptchaField {
@ -89,14 +99,35 @@ pub struct CaptchaField {
pub required_feedback: String, pub required_feedback: String,
} }
fn default_title() -> String {
"Contact form".to_string()
}
fn default_optional() -> String {
"optional".to_string()
}
fn default_submit() -> String {
"Submit".to_string()
}
fn default_success() -> String {
"Your request has been successfully submitted. We will get back to you soon.".to_string()
}
fn default_message_from() -> String {
"Message from".to_string()
}
/// Localization strings. /// Localization strings.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Strings { pub struct Strings {
#[serde(default)]
pub description: String, pub description: String,
#[serde(default = "default_title")]
pub title: String, pub title: String,
#[serde(default = "default_optional")]
pub optional: String, pub optional: String,
#[serde(default = "default_submit")]
pub submit: String, pub submit: String,
#[serde(default = "default_success")]
pub success: String, pub success: String,
#[serde(default = "default_message_from")]
pub message_from: String, pub message_from: String,
pub name_field: NameField, pub name_field: NameField,
pub email_field: EmailField, pub email_field: EmailField,
@ -104,13 +135,16 @@ pub struct Strings {
pub error_messages: ErrorMessages, pub error_messages: ErrorMessages,
} }
fn default_path_prefix() -> String {
"/".to_string()
}
fn default_lang() -> String { fn default_lang() -> String {
"en".to_string() "en".to_string()
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct StateConfig { pub struct StateConfig {
/// The path prefix of all routes. /// The path prefix of all routes.
#[serde(default = "default_path_prefix")]
pub path_prefix: String, pub path_prefix: String,
pub custom_fields: Vec<CustomField>, pub custom_fields: Vec<CustomField>,
/// The language tag of the HTML file. /// The language tag of the HTML file.
@ -119,29 +153,35 @@ pub struct StateConfig {
pub strings: Strings, pub strings: Strings,
} }
fn default_socket_address() -> String {
"0.0.0.0:80".to_string()
}
/// Configuration. /// Configuration.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Config { pub struct Config {
/// The server socket address including port. /// The server socket address including port.
#[serde(default = "default_socket_address")]
pub socket_address: String, pub socket_address: String,
pub email: Email, pub email: Email,
pub log_file: String,
pub utc_offset: UtcOffset, pub utc_offset: UtcOffset,
#[serde(flatten)] #[serde(flatten)]
pub state_config: StateConfig, pub state_config: StateConfig,
} }
impl Config { impl Config {
/// Parses the configuration from the config path in the environment variable. /// Parses the configuration in the given data directory.
pub fn build() -> Result<Self> { pub fn build(data_dir: &Path) -> Result<Self> {
// The environment variable with the path to the config file. let reader = {
let config_file_var = "CF_CONFIG_FILE"; let mut buf = data_dir.to_path_buf();
let config_path = env::var(config_file_var) buf.push("config.yaml");
.with_context(|| format!("Environment variable {config_file_var} missing!"))?;
let file = File::open(&buf).with_context(|| {
format!("Can not open the config file at the path {}", buf.display())
})?;
BufReader::new(file)
};
let file = File::open(&config_path)
.with_context(|| format!("Can not open the config file at the path {config_path}"))?;
let reader = BufReader::new(file);
let mut config: Self = let mut config: Self =
serde_yaml::from_reader(reader).context("Can not parse the YAML config file!")?; serde_yaml::from_reader(reader).context("Can not parse the YAML config file!")?;

View file

@ -1,5 +1,5 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use std::fs::OpenOptions; use std::{fs::OpenOptions, path::Path};
use time::{format_description::well_known::Rfc3339, UtcOffset}; use time::{format_description::well_known::Rfc3339, UtcOffset};
use tracing_subscriber::{ use tracing_subscriber::{
filter::LevelFilter, filter::LevelFilter,
@ -9,11 +9,13 @@ use tracing_subscriber::{
Layer, Layer,
}; };
use crate::config;
/// Initializes the logger. /// Initializes the logger.
pub fn init_logger(log_file: &str, utc_offset_hours: i8, utc_offset_minutes: i8) -> Result<()> { pub fn init_logger(data_dir: &Path, utc_offset: &config::UtcOffset) -> Result<()> {
// Set UTC offset for time formatting. // Set UTC offset for time formatting.
let timer = OffsetTime::new( let timer = OffsetTime::new(
UtcOffset::from_hms(utc_offset_hours, utc_offset_minutes, 0) UtcOffset::from_hms(utc_offset.hours, utc_offset.minutes, 0)
.context("Failed to set the time offset from the given utc_hours_offset!")?, .context("Failed to set the time offset from the given utc_hours_offset!")?,
Rfc3339, Rfc3339,
); );
@ -31,11 +33,16 @@ pub fn init_logger(log_file: &str, utc_offset_hours: i8, utc_offset_minutes: i8)
.with_filter(stdout_level_filter); .with_filter(stdout_level_filter);
// Log file. // Log file.
let log_file = OpenOptions::new() let log_file = {
let mut buf = data_dir.to_path_buf();
buf.push("log.txt");
OpenOptions::new()
.create(true) .create(true)
.append(true) .append(true)
.open(log_file) .open(buf)
.context("Failed to open the log file in append mode!")?; .context("Failed to open the log file in append mode!")?
};
let file_layer = fmt::layer() let file_layer = fmt::layer()
.with_writer(log_file) .with_writer(log_file)
.with_ansi(false) .with_ansi(false)

View file

@ -13,20 +13,23 @@ use axum::{
routing::{get, get_service, Router}, routing::{get, get_service, Router},
Server, Server,
}; };
use std::{net::SocketAddr, process}; use std::{env, net::SocketAddr, path::PathBuf, process};
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use tracing::{error, info}; use tracing::{error, info};
use crate::{config::Config, states::AppState}; use crate::{config::Config, states::AppState};
async fn init(logger_initialized: &mut bool) -> Result<()> { const DATA_DIR_ENV_VAR: &str = "CF_DATA_DIR";
let config = Config::build()?;
logging::init_logger( async fn init(logger_initialized: &mut bool) -> Result<()> {
&config.log_file, let data_dir = PathBuf::from(
config.utc_offset.hours, env::var(DATA_DIR_ENV_VAR)
config.utc_offset.minutes, .with_context(|| format!("Environment variable {DATA_DIR_ENV_VAR} missing!"))?,
)?; );
let config = Config::build(&data_dir)?;
logging::init_logger(&data_dir, &config.utc_offset)?;
*logger_initialized = true; *logger_initialized = true;
// The path prefix of all routes. // The path prefix of all routes.