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:
@@ -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.
|
||||
|
||||
130
src/bin/list_files_example.rs
Normal file
130
src/bin/list_files_example.rs
Normal 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
|
||||
}
|
||||
}
|
||||
48
src/main.rs
48
src/main.rs
@@ -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...");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user