code: updating justfile with watch command, adding logging functionality, control+c detection, host/port loading from env, and a file listing example for an alternative source besides databases

This commit is contained in:
2023-12-30 17:59:04 +01:00
parent 0548b910b1
commit da01827419
7 changed files with 238 additions and 3 deletions

View File

@@ -152,7 +152,7 @@ pub async fn index_action(Form(example_data): Form<ExampleData>) -> impl IntoRes
// List Items renders the entire list item page.
pub async fn list_item_collection(templates: State<templates::Templates>) -> impl IntoResponse {
templates.render_html("admin/list_items.html", ())
templates.render_html("admin/items/list_items.html", ())
}
// Items Action is a POST to an item list. By default these are actions, that work on a list of items as input.

View File

@@ -0,0 +1,130 @@
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug)]
pub struct PathEntry {
path: PathBuf,
file_name: String,
is_dir: bool,
is_file: bool,
is_symlink: bool,
}
impl std::fmt::Display for PathEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.path.to_string_lossy())
}
}
struct FolderScanner {
base_path: PathBuf,
}
impl FolderScanner {
// Create a new FolderScanner with a base path
fn new(base_path: &str) -> Self {
FolderScanner {
base_path: PathBuf::from(base_path),
}
}
// Scan the folder with optional filters
fn scan_folder(&self, folder: &str, filters: Option<Filters>) -> Vec<PathEntry> {
let base_path_clean = dunce::canonicalize(&self.base_path).unwrap();
let full_path = dunce::canonicalize(self.base_path.join(folder)).unwrap();
println!("Scanning folder: {}", &full_path.display());
// Ensure that the path is within the base path
if !full_path.starts_with(&base_path_clean) {
panic!("Access to the folder outside the base path is not allowed.");
}
let entries = fs::read_dir(full_path).unwrap();
let mut files = Vec::new();
for entry in entries {
if let Ok(dir_entry) = entry {
let path = dir_entry.path();
if let Some(f) = &filters {
if f.should_filter(&path) {
continue;
}
}
let (is_dir, is_file, is_simlink) = if let Ok(metadata) = dir_entry.metadata() {
(metadata.is_dir(), metadata.is_file(), metadata.is_symlink())
} else {
(false, false, false)
};
files.push(PathEntry {
path: path,
file_name: dir_entry.file_name().to_string_lossy().into_owned(),
is_dir: is_dir,
is_file: is_file,
is_symlink: is_simlink,
});
}
}
files
}
}
struct Filters {
exclude_dot_files: bool,
exclude_folders: bool,
exclude_symlinks: bool,
allowed_extensions: Vec<String>,
}
impl Filters {
fn should_filter(&self, path: &Path) -> bool {
if self.exclude_dot_files && path.file_name().unwrap().to_str().unwrap().starts_with('.') {
return true;
}
if self.exclude_folders && path.is_dir() {
return true;
}
if self.exclude_symlinks && fs::symlink_metadata(path).unwrap().file_type().is_symlink() {
return true;
}
if !self.allowed_extensions.is_empty() {
if let Some(ext) = path.extension() {
if !self
.allowed_extensions
.contains(&ext.to_str().unwrap().to_string())
{
return true;
}
} else {
return true;
}
}
false
}
}
fn main() {
let scanner = FolderScanner::new(".");
// Example usage
let filters = Filters {
exclude_dot_files: false,
exclude_folders: false,
exclude_symlinks: false,
allowed_extensions: vec!["md".to_string(), "rs".to_string()],
};
let files = scanner.scan_folder("src", Some(filters));
for file in files {
println!("{}", file); // Display
println!("{:?}", file); // Debug
}
}

View File

@@ -8,6 +8,9 @@ use crate::state::AppState;
use axum::{
extract::State, handler::HandlerWithoutStateExt, response::IntoResponse, routing::get, Router,
};
use dotenvy::dotenv;
use log::info;
use std::env;
use std::net::SocketAddr;
use std::sync::Arc;
@@ -21,6 +24,11 @@ async fn hello_world(templates: State<templates::Templates>) -> impl IntoRespons
#[tokio::main]
async fn main() {
// Load environment
dotenv().ok();
env_logger::init();
info!("Miniweb starting...");
// Prepare App State
let tmpl = templates::Templates::initialize().expect("Template Engine could not be loaded.");
let mut admin = admin::state::AdminRegistry::new("admin");
@@ -43,10 +51,46 @@ async fn main() {
.with_state(state);
// Run Server
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("listening on {}", addr);
let app_host: std::net::IpAddr = env::var("APP_HOST")
.unwrap_or("127.0.0.1".to_string())
.parse()
.expect("IP Address expected in APP_HOST");
let app_port: u16 = env::var("APP_PORT")
.unwrap_or("3000".to_string())
.parse()
.expect("Port expected in APP_PORT");
let addr = SocketAddr::from((app_host, app_port));
info!("listening on {}", addr);
info!("admin on: http://{}/admin", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.with_graceful_shutdown(shutdown_signal())
.await
.unwrap();
}
async fn shutdown_signal() {
let ctrl_c = async {
tokio::signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
info!("shutting down...");
}