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:
parent
0548b910b1
commit
da01827419
48
Cargo.lock
generated
48
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"
|
||||
|
4
Justfile
4
Justfile
@ -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:
|
||||
|
@ -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...");
|
||||
}
|
||||
|
5
templates/admin/items/list_items.html
Normal file
5
templates/admin/items/list_items.html
Normal file
@ -0,0 +1,5 @@
|
||||
{% if item_list %}
|
||||
{% for item in item_list %}
|
||||
poop: {{ item}}
|
||||
{% endfor %}
|
||||
{% endif %}
|
Loading…
Reference in New Issue
Block a user