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:
Gabor Körber 2023-12-30 17:59:04 +01:00
parent 0548b910b1
commit da01827419
7 changed files with 238 additions and 3 deletions

48
Cargo.lock generated
View File

@ -790,6 +790,12 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dunce"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
[[package]]
name = "either"
version = "1.9.0"
@ -808,6 +814,19 @@ dependencies = [
"tokio",
]
[[package]]
name = "env_logger"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
dependencies = [
"humantime",
"is-terminal",
"log",
"regex",
"termcolor",
]
[[package]]
name = "equivalent"
version = "1.0.1"
@ -1243,6 +1262,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.27"
@ -1360,6 +1385,17 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "is-terminal"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
dependencies = [
"hermit-abi",
"rustix 0.38.28",
"windows-sys 0.52.0",
]
[[package]]
name = "itertools"
version = "0.11.0"
@ -1573,7 +1609,10 @@ dependencies = [
"axum",
"barrel",
"dotenvy",
"dunce",
"entity",
"env_logger",
"log",
"mime_guess",
"minijinja",
"minijinja-autoreload",
@ -3033,6 +3072,15 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "termcolor"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.51"

View File

@ -3,6 +3,7 @@ name = "miniweb"
version = "0.1.0"
edition = "2021"
publish = false
default-run = "miniweb"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -42,3 +43,6 @@ rust-embed = { version = "8.0.0", features = [
] }
serde = { version = "1.0.188", features = ["derive"] }
tokio = { version = "1.32.0", features = ["full"] }
dunce = "1.0.4"
log = "0.4.20"
env_logger = "0.10.1"

View File

@ -8,6 +8,9 @@ default:
run args='miniweb':
@cargo run --bin {{args}}
watch:
cargo watch -c -q -w src -x run
status:
@echo "Docker Images:"
cd docker && docker-compose ls
@ -29,6 +32,7 @@ migrate:
# Install Developer dependencies
dev-install:
cargo install sea-orm-cli
cargo install cargo-watch
# Reset Database
dev-reset:

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...");
}

View File

@ -0,0 +1,5 @@
{% if item_list %}
{% for item in item_list %}
poop: {{ item}}
{% endfor %}
{% endif %}