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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dunce"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
@ -808,6 +814,19 @@ dependencies = [
|
|||||||
"tokio",
|
"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]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -1243,6 +1262,12 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.27"
|
version = "0.14.27"
|
||||||
@ -1360,6 +1385,17 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"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]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -1573,7 +1609,10 @@ dependencies = [
|
|||||||
"axum",
|
"axum",
|
||||||
"barrel",
|
"barrel",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"dunce",
|
||||||
"entity",
|
"entity",
|
||||||
|
"env_logger",
|
||||||
|
"log",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"minijinja",
|
"minijinja",
|
||||||
"minijinja-autoreload",
|
"minijinja-autoreload",
|
||||||
@ -3033,6 +3072,15 @@ dependencies = [
|
|||||||
"windows-sys 0.48.0",
|
"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]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.51"
|
version = "1.0.51"
|
||||||
|
@ -3,6 +3,7 @@ name = "miniweb"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
default-run = "miniweb"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# 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"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
tokio = { version = "1.32.0", features = ["full"] }
|
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':
|
run args='miniweb':
|
||||||
@cargo run --bin {{args}}
|
@cargo run --bin {{args}}
|
||||||
|
|
||||||
|
watch:
|
||||||
|
cargo watch -c -q -w src -x run
|
||||||
|
|
||||||
status:
|
status:
|
||||||
@echo "Docker Images:"
|
@echo "Docker Images:"
|
||||||
cd docker && docker-compose ls
|
cd docker && docker-compose ls
|
||||||
@ -29,6 +32,7 @@ migrate:
|
|||||||
# Install Developer dependencies
|
# Install Developer dependencies
|
||||||
dev-install:
|
dev-install:
|
||||||
cargo install sea-orm-cli
|
cargo install sea-orm-cli
|
||||||
|
cargo install cargo-watch
|
||||||
|
|
||||||
# Reset Database
|
# Reset Database
|
||||||
dev-reset:
|
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.
|
// List Items renders the entire list item page.
|
||||||
pub async fn list_item_collection(templates: State<templates::Templates>) -> impl IntoResponse {
|
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.
|
// 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::{
|
use axum::{
|
||||||
extract::State, handler::HandlerWithoutStateExt, response::IntoResponse, routing::get, Router,
|
extract::State, handler::HandlerWithoutStateExt, response::IntoResponse, routing::get, Router,
|
||||||
};
|
};
|
||||||
|
use dotenvy::dotenv;
|
||||||
|
use log::info;
|
||||||
|
use std::env;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -21,6 +24,11 @@ async fn hello_world(templates: State<templates::Templates>) -> impl IntoRespons
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
// Load environment
|
||||||
|
dotenv().ok();
|
||||||
|
env_logger::init();
|
||||||
|
info!("Miniweb starting...");
|
||||||
|
|
||||||
// Prepare App State
|
// Prepare App State
|
||||||
let tmpl = templates::Templates::initialize().expect("Template Engine could not be loaded.");
|
let tmpl = templates::Templates::initialize().expect("Template Engine could not be loaded.");
|
||||||
let mut admin = admin::state::AdminRegistry::new("admin");
|
let mut admin = admin::state::AdminRegistry::new("admin");
|
||||||
@ -43,10 +51,46 @@ async fn main() {
|
|||||||
.with_state(state);
|
.with_state(state);
|
||||||
|
|
||||||
// Run Server
|
// Run Server
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
let app_host: std::net::IpAddr = env::var("APP_HOST")
|
||||||
println!("listening on {}", addr);
|
.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)
|
axum::Server::bind(&addr)
|
||||||
.serve(app.into_make_service())
|
.serve(app.into_make_service())
|
||||||
|
.with_graceful_shutdown(shutdown_signal())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.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