2023-02-08 20:49:05 +01:00
|
|
|
use serde::Deserialize;
|
2023-04-09 19:44:24 +02:00
|
|
|
use std::{fs, path::PathBuf};
|
2023-04-09 16:58:21 +02:00
|
|
|
|
|
|
|
use glob::glob;
|
|
|
|
use std::{fs::File, io::Write, path::Path};
|
2023-02-08 20:49:05 +01:00
|
|
|
|
2023-04-09 19:26:20 +02:00
|
|
|
use crate::template::Template;
|
|
|
|
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Store the configuration of config/config.toml
|
2024-04-01 18:11:46 +02:00
|
|
|
#[derive(Clone, Debug, Default, Deserialize)]
|
2024-05-28 20:26:58 +02:00
|
|
|
pub struct FileConfiguration {
|
2023-04-21 19:47:47 +02:00
|
|
|
/// http/https
|
2023-02-09 11:36:22 +01:00
|
|
|
pub scheme: Option<String>,
|
2023-10-24 11:50:30 +02:00
|
|
|
/// Domain name "sub.domain.tld"
|
|
|
|
pub domain: Option<String>,
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Port used
|
2023-02-09 11:36:22 +01:00
|
|
|
pub port: Option<u16>,
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Mail of owner
|
2023-02-08 22:14:57 +01:00
|
|
|
pub mail: Option<String>,
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Lang used
|
2023-02-08 22:14:57 +01:00
|
|
|
pub lang: Option<String>,
|
2023-10-24 11:50:30 +02:00
|
|
|
/// .onion address for Tor of the app
|
2023-02-16 21:59:04 +01:00
|
|
|
pub onion: Option<String>,
|
2023-04-21 19:47:47 +02:00
|
|
|
/// App name
|
2023-04-11 00:57:30 +02:00
|
|
|
pub app_name: Option<String>,
|
2023-04-26 16:21:57 +02:00
|
|
|
/// Name of website owner
|
|
|
|
pub name: Option<String>,
|
2023-04-28 12:49:48 +02:00
|
|
|
/// Fullname of website owner
|
|
|
|
pub fullname: Option<String>,
|
2024-04-01 18:11:46 +02:00
|
|
|
/// List exclusion for courses
|
|
|
|
pub exclude_courses: Option<Vec<String>>,
|
2023-02-08 20:49:05 +01:00
|
|
|
}
|
|
|
|
|
2024-05-28 20:26:58 +02:00
|
|
|
impl FileConfiguration {
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Initialize with default values
|
2023-02-16 15:58:43 +01:00
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
2023-04-26 12:07:46 +02:00
|
|
|
scheme: Some("http".into()),
|
2023-10-24 11:50:30 +02:00
|
|
|
domain: Some("localhost".into()),
|
2023-02-16 15:58:43 +01:00
|
|
|
port: Some(8080),
|
2023-04-26 12:07:46 +02:00
|
|
|
app_name: Some("EWP".into()),
|
2024-04-01 18:11:46 +02:00
|
|
|
exclude_courses: Some([].into()),
|
2024-05-28 20:58:41 +02:00
|
|
|
..Self::default()
|
2023-02-16 15:58:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Complete default structure with an existing one
|
2023-02-16 15:58:43 +01:00
|
|
|
fn complete(a: Self) -> Self {
|
|
|
|
// Default config
|
2024-05-28 20:58:41 +02:00
|
|
|
let d = Self::new();
|
2023-02-16 15:58:43 +01:00
|
|
|
|
2024-05-28 20:26:58 +02:00
|
|
|
#[allow(clippy::items_after_statements)]
|
2023-02-16 15:58:43 +01:00
|
|
|
/// Return the default value if nothing is value is none
|
|
|
|
fn test<T>(val: Option<T>, default: Option<T>) -> Option<T> {
|
|
|
|
if val.is_some() {
|
|
|
|
val
|
|
|
|
} else {
|
|
|
|
default
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Self {
|
|
|
|
scheme: test(a.scheme, d.scheme),
|
|
|
|
port: test(a.port, d.port),
|
|
|
|
mail: test(a.mail, d.mail),
|
|
|
|
lang: test(a.lang, d.lang),
|
2023-10-24 11:50:30 +02:00
|
|
|
domain: test(a.domain, d.domain),
|
2023-02-16 21:59:04 +01:00
|
|
|
onion: test(a.onion, d.onion),
|
2023-04-11 00:57:30 +02:00
|
|
|
app_name: test(a.app_name, d.app_name),
|
2023-04-26 16:21:57 +02:00
|
|
|
name: test(a.name, d.name),
|
2023-04-28 12:49:48 +02:00
|
|
|
fullname: test(a.fullname, d.fullname),
|
2024-04-01 18:11:46 +02:00
|
|
|
exclude_courses: test(a.exclude_courses, d.exclude_courses),
|
2023-02-16 15:58:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-24 11:52:20 +01:00
|
|
|
// Paths where files are stored
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct Locations {
|
|
|
|
pub static_dir: String,
|
|
|
|
pub data_dir: String,
|
|
|
|
}
|
|
|
|
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Configuration used internally in the app
|
2023-05-02 13:34:43 +02:00
|
|
|
#[derive(Clone, Debug)]
|
2023-04-09 19:26:20 +02:00
|
|
|
pub struct Config {
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Information given in the config file
|
2024-05-28 20:26:58 +02:00
|
|
|
pub fc: FileConfiguration,
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Location where the static files are stored
|
2024-01-24 11:52:20 +01:00
|
|
|
pub locations: Locations,
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Informations about templates
|
2023-04-09 19:26:20 +02:00
|
|
|
pub tmpl: Template,
|
|
|
|
}
|
|
|
|
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Load the config file
|
2024-05-28 20:26:58 +02:00
|
|
|
fn get_file_config(file_path: &str) -> FileConfiguration {
|
2024-05-28 20:58:41 +02:00
|
|
|
fs::read_to_string(file_path).map_or_else(
|
|
|
|
|_| FileConfiguration::new(),
|
|
|
|
|file| match toml::from_str(&file) {
|
2024-05-28 20:26:58 +02:00
|
|
|
Ok(stored_config) => FileConfiguration::complete(stored_config),
|
2023-02-08 20:49:05 +01:00
|
|
|
Err(file_error) => {
|
|
|
|
panic!("Error in config file: {file_error}");
|
|
|
|
}
|
|
|
|
},
|
2024-05-28 20:58:41 +02:00
|
|
|
)
|
2023-02-08 20:49:05 +01:00
|
|
|
}
|
2023-04-09 15:19:23 +02:00
|
|
|
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Build the configuration
|
2024-05-28 20:26:58 +02:00
|
|
|
pub fn get_configuration(file_path: &str) -> Config {
|
2023-04-09 15:19:23 +02:00
|
|
|
let internal_config = get_file_config(file_path);
|
|
|
|
|
2024-05-28 20:26:58 +02:00
|
|
|
let static_dir = "static";
|
|
|
|
let templates_dir = "templates";
|
|
|
|
let files_root = init("dist".into(), static_dir, templates_dir);
|
2023-04-09 16:58:21 +02:00
|
|
|
|
2023-04-09 15:19:23 +02:00
|
|
|
Config {
|
2024-05-28 20:26:58 +02:00
|
|
|
fc: internal_config.clone(),
|
2024-01-24 11:52:20 +01:00
|
|
|
locations: Locations {
|
2024-05-28 20:26:58 +02:00
|
|
|
static_dir: format!("{files_root}/{static_dir}"),
|
2024-01-24 11:52:20 +01:00
|
|
|
data_dir: String::from("data"),
|
|
|
|
},
|
2023-04-09 19:26:20 +02:00
|
|
|
tmpl: Template {
|
2024-05-28 20:26:58 +02:00
|
|
|
directory: format!("{files_root}/{templates_dir}"),
|
2023-04-11 01:08:07 +02:00
|
|
|
app_name: internal_config.app_name.unwrap(),
|
2023-10-24 11:50:30 +02:00
|
|
|
url: internal_config.domain.unwrap(),
|
2023-10-24 12:21:30 +02:00
|
|
|
name: internal_config.name,
|
2023-04-09 19:26:20 +02:00
|
|
|
},
|
2023-04-09 16:58:21 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Preparation before running the http server
|
2024-05-28 20:26:58 +02:00
|
|
|
fn init(dist_dir: String, static_dir: &str, templates_dir: &str) -> String {
|
2023-04-09 18:23:51 +02:00
|
|
|
// The static folder is minimized only in release mode
|
|
|
|
if cfg!(debug_assertions) {
|
2023-04-26 12:07:46 +02:00
|
|
|
".".into()
|
2023-04-09 18:23:51 +02:00
|
|
|
} else {
|
2023-04-24 15:43:08 +02:00
|
|
|
let cfg = minify_html::Cfg {
|
2023-04-11 02:31:30 +02:00
|
|
|
keep_closing_tags: true,
|
2024-06-02 18:44:46 +02:00
|
|
|
preserve_brace_template_syntax: true,
|
2023-04-11 02:31:30 +02:00
|
|
|
minify_css: true,
|
|
|
|
minify_js: true,
|
2023-04-24 15:43:08 +02:00
|
|
|
..minify_html::Cfg::spec_compliant()
|
2023-04-11 02:31:30 +02:00
|
|
|
};
|
2023-04-09 18:23:51 +02:00
|
|
|
|
|
|
|
// Static files
|
|
|
|
for entry in glob(&format!("{static_dir}/**/*.*")).unwrap() {
|
|
|
|
let path = entry.unwrap();
|
|
|
|
let path_with_dist = path
|
|
|
|
.to_string_lossy()
|
2024-05-28 20:26:58 +02:00
|
|
|
.replace(static_dir, &format!("{dist_dir}/{static_dir}"));
|
2023-04-09 18:23:51 +02:00
|
|
|
|
|
|
|
minify_and_copy(&cfg, path, path_with_dist);
|
|
|
|
}
|
2023-04-09 16:58:21 +02:00
|
|
|
|
2023-04-09 18:23:51 +02:00
|
|
|
// Template files
|
|
|
|
for entry in glob(&format!("{templates_dir}/**/*.*")).unwrap() {
|
|
|
|
let path = entry.unwrap();
|
|
|
|
let path_with_dist = path
|
|
|
|
.to_string_lossy()
|
2024-05-28 20:26:58 +02:00
|
|
|
.replace(templates_dir, &format!("{dist_dir}/{templates_dir}"));
|
2023-04-09 17:35:15 +02:00
|
|
|
|
2023-04-09 18:23:51 +02:00
|
|
|
minify_and_copy(&cfg, path, path_with_dist);
|
|
|
|
}
|
2023-04-09 17:35:15 +02:00
|
|
|
|
2023-04-09 18:23:51 +02:00
|
|
|
dist_dir
|
2023-04-09 17:35:15 +02:00
|
|
|
}
|
2023-04-09 16:58:21 +02:00
|
|
|
}
|
|
|
|
|
2023-04-21 19:47:47 +02:00
|
|
|
/// Minify some assets for production
|
2023-04-24 15:43:08 +02:00
|
|
|
fn minify_and_copy(cfg: &minify_html::Cfg, path: PathBuf, path_with_dist: String) {
|
2023-04-09 16:58:21 +02:00
|
|
|
// Create folders
|
|
|
|
let new_path = Path::new(&path_with_dist);
|
|
|
|
fs::create_dir_all(new_path.parent().unwrap()).unwrap();
|
|
|
|
|
2023-04-24 15:43:08 +02:00
|
|
|
let session = minify_js::Session::new();
|
2023-04-09 16:58:21 +02:00
|
|
|
let mut copy = true;
|
|
|
|
if let Some(ext) = path.extension() {
|
2023-04-24 15:43:08 +02:00
|
|
|
let js_ext = "js";
|
|
|
|
let current_ext = ext.to_string_lossy().to_lowercase();
|
2023-04-09 16:58:21 +02:00
|
|
|
// List of files who should be minified
|
2023-04-24 15:43:08 +02:00
|
|
|
if ["html", "css", js_ext, "svg", "webmanifest", "xml"].contains(¤t_ext.as_str()) {
|
2023-04-09 16:58:21 +02:00
|
|
|
// We won't copy, we'll minify
|
|
|
|
copy = false;
|
|
|
|
|
|
|
|
// Minify
|
|
|
|
let data = fs::read(&path).unwrap();
|
2023-04-24 15:43:08 +02:00
|
|
|
let minified = if current_ext == js_ext {
|
|
|
|
let mut out = Vec::new();
|
|
|
|
minify_js::minify(&session, minify_js::TopLevelMode::Global, &data, &mut out)
|
|
|
|
.unwrap();
|
|
|
|
out
|
|
|
|
} else {
|
|
|
|
minify_html::minify(&data, cfg)
|
|
|
|
};
|
2023-04-09 16:58:21 +02:00
|
|
|
|
|
|
|
// Write files
|
|
|
|
let file = File::create(&path_with_dist);
|
|
|
|
file.expect("Error when minify the file")
|
|
|
|
.write_all(&minified)
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if copy {
|
|
|
|
// If no minification is needed
|
|
|
|
fs::copy(path, path_with_dist).unwrap();
|
2023-04-09 15:19:23 +02:00
|
|
|
}
|
|
|
|
}
|