2022-10-29 17:09:56 +00:00
|
|
|
use anyhow::{Context, Result};
|
2022-10-28 22:41:02 +00:00
|
|
|
use serde::Deserialize;
|
2023-02-26 13:51:57 +00:00
|
|
|
use std::{fs::File, io::BufReader, path::Path};
|
2022-10-28 22:41:02 +00:00
|
|
|
|
2023-02-23 16:10:24 +00:00
|
|
|
/// Email server credentials.
|
2022-10-28 22:41:02 +00:00
|
|
|
#[derive(Deserialize)]
|
2023-02-25 17:08:21 +00:00
|
|
|
pub struct EmailCredentials {
|
|
|
|
pub domain: String,
|
|
|
|
pub username: String,
|
2022-10-28 22:41:02 +00:00
|
|
|
pub password: String,
|
|
|
|
}
|
|
|
|
|
2023-02-25 17:08:21 +00:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub struct Email {
|
|
|
|
pub from: String,
|
|
|
|
pub to: String,
|
|
|
|
pub credentials: EmailCredentials,
|
|
|
|
}
|
|
|
|
|
2023-02-23 16:10:24 +00:00
|
|
|
/// UTC offset for time formatting.
|
2023-02-26 15:47:44 +00:00
|
|
|
#[derive(Deserialize, Default)]
|
|
|
|
#[serde(default)]
|
2023-02-23 02:22:31 +00:00
|
|
|
pub struct UtcOffset {
|
|
|
|
pub hours: i8,
|
|
|
|
pub minutes: i8,
|
2022-12-03 16:08:23 +00:00
|
|
|
}
|
|
|
|
|
2023-02-25 17:08:21 +00:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
#[serde(tag = "type")]
|
|
|
|
pub enum CustomFieldType {
|
|
|
|
Text,
|
|
|
|
Textarea { rows: u8 },
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub struct CustomField {
|
2023-02-25 20:14:58 +00:00
|
|
|
pub key: String,
|
2023-02-25 17:08:21 +00:00
|
|
|
pub label: String,
|
|
|
|
#[serde(default)]
|
|
|
|
pub required_feedback: Option<String>,
|
|
|
|
pub field_type: CustomFieldType,
|
|
|
|
}
|
|
|
|
|
2023-02-23 16:10:24 +00:00
|
|
|
/// Error messages for localization.
|
2022-12-03 16:08:23 +00:00
|
|
|
#[derive(Deserialize)]
|
2023-02-26 15:47:44 +00:00
|
|
|
#[serde(default)]
|
2022-12-03 16:08:23 +00:00
|
|
|
pub struct ErrorMessages {
|
|
|
|
pub captcha_error: String,
|
|
|
|
pub email_error: String,
|
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
impl Default for ErrorMessages {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
captcha_error: "You did enter the wrong code at the end of the form. Please try again."
|
|
|
|
.to_string(),
|
|
|
|
email_error:
|
|
|
|
"An internal error occurred while sending your request. Please try again later."
|
|
|
|
.to_string(),
|
|
|
|
}
|
|
|
|
}
|
2023-02-25 17:08:21 +00:00
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
|
2023-02-25 17:08:21 +00:00
|
|
|
#[derive(Deserialize)]
|
2023-02-26 15:47:44 +00:00
|
|
|
#[serde(default)]
|
2023-02-25 17:08:21 +00:00
|
|
|
pub struct NameField {
|
|
|
|
pub label: String,
|
|
|
|
pub required_feedback: String,
|
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
impl Default for NameField {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
label: "Name".to_string(),
|
|
|
|
required_feedback: "Please enter your name".to_string(),
|
|
|
|
}
|
|
|
|
}
|
2023-02-25 17:08:21 +00:00
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
|
2023-02-25 17:08:21 +00:00
|
|
|
#[derive(Deserialize)]
|
2023-02-26 15:47:44 +00:00
|
|
|
#[serde(default)]
|
2023-02-25 17:08:21 +00:00
|
|
|
pub struct EmailField {
|
|
|
|
pub label: String,
|
|
|
|
pub required_feedback: String,
|
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
impl Default for EmailField {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
label: "Email".to_string(),
|
|
|
|
required_feedback: "Please enter your email".to_string(),
|
|
|
|
}
|
|
|
|
}
|
2023-02-25 17:08:21 +00:00
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
|
2022-12-03 16:08:23 +00:00
|
|
|
#[derive(Deserialize)]
|
2023-02-26 15:47:44 +00:00
|
|
|
#[serde(default)]
|
2023-02-25 17:08:21 +00:00
|
|
|
pub struct CaptchaField {
|
2022-12-03 16:08:23 +00:00
|
|
|
pub label: String,
|
2023-02-25 17:08:21 +00:00
|
|
|
pub required_feedback: String,
|
2022-12-03 16:08:23 +00:00
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
impl Default for CaptchaField {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
label: "Enter the code above".to_string(),
|
|
|
|
required_feedback: "Please enter the code from the image above".to_string(),
|
|
|
|
}
|
|
|
|
}
|
2023-02-26 13:51:57 +00:00
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
|
2023-02-23 16:10:24 +00:00
|
|
|
/// Localization strings.
|
2022-12-03 16:08:23 +00:00
|
|
|
#[derive(Deserialize)]
|
2023-02-26 15:47:44 +00:00
|
|
|
#[serde(default)]
|
2022-12-03 16:08:23 +00:00
|
|
|
pub struct Strings {
|
|
|
|
pub description: String,
|
|
|
|
pub title: String,
|
2023-02-25 17:08:21 +00:00
|
|
|
pub optional: String,
|
2022-12-03 16:08:23 +00:00
|
|
|
pub submit: String,
|
|
|
|
pub success: String,
|
2023-02-25 17:08:21 +00:00
|
|
|
pub message_from: String,
|
|
|
|
pub name_field: NameField,
|
|
|
|
pub email_field: EmailField,
|
|
|
|
pub captcha_field: CaptchaField,
|
|
|
|
pub error_messages: ErrorMessages,
|
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
impl Default for Strings {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
description: String::default(),
|
|
|
|
title: "Contact form".to_string(),
|
|
|
|
optional: "optional".to_string(),
|
|
|
|
submit: "Submit".to_string(),
|
|
|
|
success: "Your request has been successfully submitted. We will get back to you soon."
|
|
|
|
.to_string(),
|
|
|
|
message_from: "Message from".to_string(),
|
|
|
|
name_field: NameField::default(),
|
|
|
|
email_field: EmailField::default(),
|
|
|
|
captcha_field: CaptchaField::default(),
|
|
|
|
error_messages: ErrorMessages::default(),
|
|
|
|
}
|
|
|
|
}
|
2022-11-01 23:24:17 +00:00
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
|
2022-10-28 22:41:02 +00:00
|
|
|
#[derive(Deserialize)]
|
2023-02-25 17:08:21 +00:00
|
|
|
pub struct StateConfig {
|
2023-02-23 16:10:24 +00:00
|
|
|
/// The path prefix of all routes.
|
2023-02-26 16:02:09 +00:00
|
|
|
#[serde(default)]
|
2022-10-28 22:41:02 +00:00
|
|
|
pub path_prefix: String,
|
2023-02-25 17:08:21 +00:00
|
|
|
pub custom_fields: Vec<CustomField>,
|
|
|
|
/// The language tag of the HTML file.
|
|
|
|
#[serde(default = "default_lang")]
|
|
|
|
pub lang: String,
|
2023-02-26 16:01:46 +00:00
|
|
|
#[serde(default)]
|
2023-02-25 17:08:21 +00:00
|
|
|
pub strings: Strings,
|
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
fn default_lang() -> String {
|
|
|
|
"en".to_string()
|
2023-02-26 13:51:57 +00:00
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
|
2023-02-25 17:08:21 +00:00
|
|
|
/// Configuration.
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
pub struct Config {
|
2023-02-23 16:10:24 +00:00
|
|
|
/// The server socket address including port.
|
2023-02-26 13:51:57 +00:00
|
|
|
#[serde(default = "default_socket_address")]
|
2023-02-23 02:22:31 +00:00
|
|
|
pub socket_address: String,
|
2023-02-25 17:08:21 +00:00
|
|
|
pub email: Email,
|
2023-02-26 16:01:46 +00:00
|
|
|
#[serde(default)]
|
2023-02-23 02:22:31 +00:00
|
|
|
pub utc_offset: UtcOffset,
|
2023-02-25 17:08:21 +00:00
|
|
|
#[serde(flatten)]
|
|
|
|
pub state_config: StateConfig,
|
2022-10-28 22:41:02 +00:00
|
|
|
}
|
2023-02-26 15:47:44 +00:00
|
|
|
fn default_socket_address() -> String {
|
|
|
|
"0.0.0.0:80".to_string()
|
|
|
|
}
|
2022-10-28 22:41:02 +00:00
|
|
|
|
|
|
|
impl Config {
|
2023-02-26 13:51:57 +00:00
|
|
|
/// 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)
|
|
|
|
};
|
|
|
|
|
2023-02-25 17:08:21 +00:00
|
|
|
let mut config: Self =
|
2023-02-23 16:10:24 +00:00
|
|
|
serde_yaml::from_reader(reader).context("Can not parse the YAML config file!")?;
|
2022-10-28 22:41:02 +00:00
|
|
|
|
2023-02-25 17:08:21 +00:00
|
|
|
// Add a space at the end for the email subject.
|
|
|
|
config.state_config.strings.message_from =
|
|
|
|
config.state_config.strings.message_from.trim().to_string() + " ";
|
|
|
|
|
2023-02-26 13:07:06 +00:00
|
|
|
// Add the optional word to fields without a required_feedback.
|
|
|
|
config
|
|
|
|
.state_config
|
|
|
|
.custom_fields
|
|
|
|
.iter_mut()
|
|
|
|
.filter(|field| field.required_feedback.is_none())
|
|
|
|
.for_each(|field| {
|
|
|
|
field.label = format!(
|
|
|
|
"{} ({})",
|
|
|
|
field.label.trim(),
|
|
|
|
config.state_config.strings.optional
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2023-02-26 16:02:09 +00:00
|
|
|
// Trim path prefix and set it to empty if it is /.
|
|
|
|
config.state_config.path_prefix = {
|
|
|
|
let path_prefix = config.state_config.path_prefix.trim();
|
|
|
|
|
|
|
|
if path_prefix == "/" {
|
|
|
|
String::default()
|
|
|
|
} else {
|
|
|
|
path_prefix.to_string()
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-10-29 17:09:56 +00:00
|
|
|
Ok(config)
|
2022-10-28 22:41:02 +00:00
|
|
|
}
|
|
|
|
}
|