Use data directory and add config defaults
This commit is contained in:
parent
4adc01c49c
commit
9f195fffce
4 changed files with 80 additions and 30 deletions
|
@ -14,7 +14,7 @@ RUN cargo build --release --bin contact-form
|
|||
|
||||
FROM docker.io/library/debian:stable-slim AS runtime
|
||||
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/static/ ./static/
|
||||
CMD ["contact-form"]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
use std::{env, fs::File, io::BufReader};
|
||||
use std::{fs::File, io::BufReader, path::Path};
|
||||
|
||||
/// Email server credentials.
|
||||
#[derive(Deserialize)]
|
||||
|
@ -20,7 +20,9 @@ pub struct Email {
|
|||
/// UTC offset for time formatting.
|
||||
#[derive(Deserialize)]
|
||||
pub struct UtcOffset {
|
||||
#[serde(default)]
|
||||
pub hours: i8,
|
||||
#[serde(default)]
|
||||
pub minutes: i8,
|
||||
}
|
||||
|
||||
|
@ -40,10 +42,18 @@ pub struct CustomField {
|
|||
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.
|
||||
#[derive(Deserialize)]
|
||||
pub struct ErrorMessages {
|
||||
#[serde(default = "default_captcha_error")]
|
||||
pub captcha_error: String,
|
||||
#[serde(default = "default_email_error")]
|
||||
pub email_error: String,
|
||||
}
|
||||
|
||||
|
@ -79,7 +89,7 @@ fn default_captcha_label() -> String {
|
|||
"Enter the code above".to_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)]
|
||||
pub struct CaptchaField {
|
||||
|
@ -89,14 +99,35 @@ pub struct CaptchaField {
|
|||
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.
|
||||
#[derive(Deserialize)]
|
||||
pub struct Strings {
|
||||
#[serde(default)]
|
||||
pub description: String,
|
||||
#[serde(default = "default_title")]
|
||||
pub title: String,
|
||||
#[serde(default = "default_optional")]
|
||||
pub optional: String,
|
||||
#[serde(default = "default_submit")]
|
||||
pub submit: String,
|
||||
#[serde(default = "default_success")]
|
||||
pub success: String,
|
||||
#[serde(default = "default_message_from")]
|
||||
pub message_from: String,
|
||||
pub name_field: NameField,
|
||||
pub email_field: EmailField,
|
||||
|
@ -104,13 +135,16 @@ pub struct Strings {
|
|||
pub error_messages: ErrorMessages,
|
||||
}
|
||||
|
||||
fn default_path_prefix() -> String {
|
||||
"/".to_string()
|
||||
}
|
||||
fn default_lang() -> String {
|
||||
"en".to_string()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct StateConfig {
|
||||
/// The path prefix of all routes.
|
||||
#[serde(default = "default_path_prefix")]
|
||||
pub path_prefix: String,
|
||||
pub custom_fields: Vec<CustomField>,
|
||||
/// The language tag of the HTML file.
|
||||
|
@ -119,29 +153,35 @@ pub struct StateConfig {
|
|||
pub strings: Strings,
|
||||
}
|
||||
|
||||
fn default_socket_address() -> String {
|
||||
"0.0.0.0:80".to_string()
|
||||
}
|
||||
/// Configuration.
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
/// The server socket address including port.
|
||||
#[serde(default = "default_socket_address")]
|
||||
pub socket_address: String,
|
||||
pub email: Email,
|
||||
pub log_file: String,
|
||||
pub utc_offset: UtcOffset,
|
||||
#[serde(flatten)]
|
||||
pub state_config: StateConfig,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
/// Parses the configuration from the config path in the environment variable.
|
||||
pub fn build() -> Result<Self> {
|
||||
// The environment variable with the path to the config file.
|
||||
let config_file_var = "CF_CONFIG_FILE";
|
||||
let config_path = env::var(config_file_var)
|
||||
.with_context(|| format!("Environment variable {config_file_var} missing!"))?;
|
||||
/// Parses the configuration in the given data directory.
|
||||
pub fn build(data_dir: &Path) -> Result<Self> {
|
||||
let reader = {
|
||||
let mut buf = data_dir.to_path_buf();
|
||||
buf.push("config.yaml");
|
||||
|
||||
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 =
|
||||
serde_yaml::from_reader(reader).context("Can not parse the YAML config file!")?;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use anyhow::{Context, Result};
|
||||
use std::fs::OpenOptions;
|
||||
use std::{fs::OpenOptions, path::Path};
|
||||
use time::{format_description::well_known::Rfc3339, UtcOffset};
|
||||
use tracing_subscriber::{
|
||||
filter::LevelFilter,
|
||||
|
@ -9,11 +9,13 @@ use tracing_subscriber::{
|
|||
Layer,
|
||||
};
|
||||
|
||||
use crate::config;
|
||||
|
||||
/// 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.
|
||||
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!")?,
|
||||
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);
|
||||
|
||||
// Log file.
|
||||
let log_file = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log_file)
|
||||
.context("Failed to open the log file in append mode!")?;
|
||||
let log_file = {
|
||||
let mut buf = data_dir.to_path_buf();
|
||||
buf.push("log.txt");
|
||||
|
||||
OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(buf)
|
||||
.context("Failed to open the log file in append mode!")?
|
||||
};
|
||||
let file_layer = fmt::layer()
|
||||
.with_writer(log_file)
|
||||
.with_ansi(false)
|
||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -13,20 +13,23 @@ use axum::{
|
|||
routing::{get, get_service, Router},
|
||||
Server,
|
||||
};
|
||||
use std::{net::SocketAddr, process};
|
||||
use std::{env, net::SocketAddr, path::PathBuf, process};
|
||||
use tower_http::services::ServeDir;
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::{config::Config, states::AppState};
|
||||
|
||||
async fn init(logger_initialized: &mut bool) -> Result<()> {
|
||||
let config = Config::build()?;
|
||||
const DATA_DIR_ENV_VAR: &str = "CF_DATA_DIR";
|
||||
|
||||
logging::init_logger(
|
||||
&config.log_file,
|
||||
config.utc_offset.hours,
|
||||
config.utc_offset.minutes,
|
||||
)?;
|
||||
async fn init(logger_initialized: &mut bool) -> Result<()> {
|
||||
let data_dir = PathBuf::from(
|
||||
env::var(DATA_DIR_ENV_VAR)
|
||||
.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;
|
||||
|
||||
// The path prefix of all routes.
|
||||
|
|
Loading…
Reference in a new issue