reorganize module

This commit is contained in:
Timur Gordon
2025-04-04 08:28:07 +02:00
parent 1ea37e2e7f
commit 939b6b4e57
375 changed files with 7580 additions and 191 deletions

View File

@@ -0,0 +1,2 @@
[alias]
dev = "watch -w src -w examples -w src/templates -w src/scripts -x 'run --example server'"

1546
components/calendar/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
[package]
name = "calendar"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }
http = "0.2"
hyper = { version = "0.14", features = ["full"] }
httparse = "1"
rhai = { version = "1.21", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tera = "1.0"
once_cell = "1"
rhai_tera = { path = "../../rhai_tera" }
utils = { path = "../../utils" }

View File

@@ -0,0 +1,51 @@
# Calendar Server Example
A simple Rust web server using Hyper that exposes an `/all_calendars` endpoint.
## Running the server
```bash
# Navigate to the examples directory
cd /path/to/examples
# Build and run the server
cargo run
```
Once the server is running, you can access the endpoint at:
- http://127.0.0.1:8080/all_calendars
## Features
- Simple HTTP server using Hyper
- Single endpoint that returns "Hello World"
Sure thing! Heres the Markdown version you can copy-paste directly into your README.md:
## 🔁 Live Reload (Hot Reload for Development)
To automatically recompile and restart your example server on file changes (e.g. Rust code, templates, Rhai scripts), you can use [`cargo-watch`](https://github.com/watchexec/cargo-watch):
### ✅ Step 1: Install `cargo-watch`
```bash
cargo install cargo-watch
```
### ✅ Step 2: Run the server with live reload
cargo watch -x 'run --example server'
This will:
• Watch for file changes in your project
• Rebuild and re-run examples/server.rs whenever you make a change
### 🧠 Bonus: Watch additional folders
To also reload when .tera templates or .rhai scripts change:
cargo watch -w src -w examples -w src/templates -w src/scripts -x 'run --example server'
### 💡 Optional: Clear terminal on each reload
cargo watch -c -x 'run --example server'

11
components/calendar/develop.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e
# 🚀 Start dev server with file watching and browser reload
reloadd \
--watch src \
--watch src/templates \
--watch examples \
--port 8080 \
--run "cargo build" \
--run "cargo run --example server"

View File

@@ -0,0 +1,119 @@
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
use hyper::{Method, StatusCode};
use std::convert::Infallible;
use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::process::Command;
use httparse;
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
// Serialize request into raw HTTP format
let raw_request = build_raw_http_request(req).await;
// Spawn the binary
let mut child = Command::new("./target/debug/calendar")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.expect("failed to spawn binary");
// Feed raw HTTP request to binary's stdin
if let Some(mut stdin) = child.stdin.take() {
let _ = stdin.write_all(&raw_request).await;
}
// Read response from binary's stdout
let mut stdout = child.stdout.take().expect("no stdout");
let mut output = Vec::new();
let _ = stdout.read_to_end(&mut output).await;
let _ = child.wait().await;
// Parse raw HTTP response
match parse_raw_http_response(&output) {
Ok(res) => Ok(res),
Err(e) => {
let mut resp = Response::new(Body::from(format!("Failed to parse response: {}", e)));
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
Ok(resp)
}
}
}
// Build raw HTTP request string
async fn build_raw_http_request(req: Request<Body>) -> Vec<u8> {
let mut raw = String::new();
let method = req.method();
let path = req.uri().path();
let version = match req.version() {
hyper::Version::HTTP_11 => "HTTP/1.1",
_ => "HTTP/1.1",
};
raw.push_str(&format!("{method} {path} {version}\r\n"));
for (key, value) in req.headers() {
if let Ok(val_str) = value.to_str() {
raw.push_str(&format!("{}: {}\r\n", key, val_str));
}
}
raw.push_str("\r\n");
let body_bytes = hyper::body::to_bytes(req.into_body()).await.unwrap_or_default();
let mut full = raw.into_bytes();
full.extend_from_slice(&body_bytes);
full
}
// Parse raw HTTP response into hyper::Response<Body>
fn parse_raw_http_response(bytes: &[u8]) -> Result<Response<Body>, Box<dyn std::error::Error>> {
let mut headers = [httparse::EMPTY_HEADER; 64];
let mut res = httparse::Response::new(&mut headers);
let parsed_len = res.parse(bytes)?.unwrap(); // returns offset
let status = res.code.unwrap_or(200);
let mut builder = Response::builder().status(status);
for h in res.headers.iter() {
builder = builder.header(h.name, std::str::from_utf8(h.value)?);
}
// Get body and append LiveReload script
let mut body = bytes[parsed_len..].to_vec();
let livereload_snippet = br#"<script>
const ws = new WebSocket("ws://localhost:35729");
ws.onmessage = (msg) => {
if (msg.data === "reload") location.reload();
};
</script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
>
"#;
body.extend_from_slice(b"\n");
body.extend_from_slice(livereload_snippet);
Ok(builder.body(Body::from(body))?)
}
#[tokio::main]
async fn main() {
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
let make_svc = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(handle_request))
});
println!("Proxy server running at http://{}", addr);
if let Err(e) = Server::bind(&addr).serve(make_svc).await {
eprintln!("server error: {}", e);
}
}

View File

@@ -0,0 +1,33 @@
use hyper::{Request, Body, Response, StatusCode};
use std::path::PathBuf;
// Import the rhai_tera module
extern crate rhai_tera;
pub fn get_calendars(_req: Request<Body>) -> Response<Body> {
let tera = setup_tera_with_rhai();
let context = tera::Context::new(); // no need to pass data — we're calling the Rhai function inside
let rendered = match tera.render("get_calendars.html.tera", &context) {
Ok(html) => Response::new(Body::from(html)),
Err(err) => {
// 🔍 Print the full error with source
eprintln!("Template render error: {:?}", err); // or use err.to_string() for cleaner message
let mut res = Response::new(Body::from(format!("Template error: {}", err)));
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
res
}
};
rendered
}
fn setup_tera_with_rhai() -> tera::Tera {
rhai_tera::setup_tera_with_rhai(
"src/templates/**/*",
"get_calendars",
PathBuf::from("../../database/scripts/calendars.rhai"),
"get_calendars"
)
}

View File

@@ -0,0 +1,19 @@
use hyper::{Body, Method, Request, Response, StatusCode};
use std::convert::Infallible;
mod controller {
pub mod calendar;
}
use controller::calendar;
pub async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
match (req.method(), req.uri().path()) {
(&Method::GET, "/calendars") => Ok(calendar::get_calendars(req)),
_ => {
let mut res = Response::new(Body::from("Not Found"));
*res.status_mut() = StatusCode::NOT_FOUND;
Ok(res)
}
}
}

View File

@@ -0,0 +1,14 @@
// Import the rhai_tera module from the root of the repo
extern crate rhai_tera;
extern crate utils;
use calendar::handle_request;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = utils::read_request_from_stdin()?;
let request = utils::parse_http_request(&input)?;
let response = handle_request(request).await?;
utils::print_http_response(response).await;
Ok(())
}

View File

@@ -0,0 +1,17 @@
<h2>Calendars</h2>
<ul>
{% for cal in get_calendars() %}
<li>
<article>
<header>
<h3>{{ cal.name }}</h3>
<p>{{ cal.description }}</p>
</header>
<footer>
<p>{{ cal.id }}</p>
<p>{{ cal.owner_id }}</p>
</footer>
</article>
</li>
{% endfor %}
</ul>

View File

@@ -0,0 +1,2 @@
[alias]
dev = "watch -w src -w examples -w src/templates -w src/scripts -x 'run --example server'"

1534
components/website/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
[package]
name = "calendar"
version = "0.1.0"
edition = "2021"
[dependencies]
hyper = { version = "0.14", features = ["full"] }
tokio = { version = "1", features = ["full"] }
http = "0.2"
httparse = "1"
rhai = { version = "1.21", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tera = "1.0"
once_cell = "1"
rhai_tera = { path = "../../rhai_tera" }

View File

@@ -0,0 +1,51 @@
# Calendar Server Example
A simple Rust web server using Hyper that exposes an `/all_calendars` endpoint.
## Running the server
```bash
# Navigate to the examples directory
cd /path/to/examples
# Build and run the server
cargo run
```
Once the server is running, you can access the endpoint at:
- http://127.0.0.1:8080/all_calendars
## Features
- Simple HTTP server using Hyper
- Single endpoint that returns "Hello World"
Sure thing! Heres the Markdown version you can copy-paste directly into your README.md:
## 🔁 Live Reload (Hot Reload for Development)
To automatically recompile and restart your example server on file changes (e.g. Rust code, templates, Rhai scripts), you can use [`cargo-watch`](https://github.com/watchexec/cargo-watch):
### ✅ Step 1: Install `cargo-watch`
```bash
cargo install cargo-watch
```
### ✅ Step 2: Run the server with live reload
cargo watch -x 'run --example server'
This will:
• Watch for file changes in your project
• Rebuild and re-run examples/server.rs whenever you make a change
### 🧠 Bonus: Watch additional folders
To also reload when .tera templates or .rhai scripts change:
cargo watch -w src -w examples -w src/templates -w src/scripts -x 'run --example server'
### 💡 Optional: Clear terminal on each reload
cargo watch -c -x 'run --example server'

11
components/website/develop.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -e
# 🚀 Start dev server with file watching and browser reload
reloadd \
--watch src \
--watch src/templates \
--watch examples \
--port 8080 \
--run "cargo build" \
--run "cargo run --example server"

View File

@@ -0,0 +1,119 @@
use hyper::{Body, Request, Response, Server};
use hyper::service::{make_service_fn, service_fn};
use hyper::{Method, StatusCode};
use std::convert::Infallible;
use std::net::SocketAddr;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::process::Command;
use httparse;
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
// Serialize request into raw HTTP format
let raw_request = build_raw_http_request(req).await;
// Spawn the binary
let mut child = Command::new("./target/debug/calendar")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.expect("failed to spawn binary");
// Feed raw HTTP request to binary's stdin
if let Some(mut stdin) = child.stdin.take() {
let _ = stdin.write_all(&raw_request).await;
}
// Read response from binary's stdout
let mut stdout = child.stdout.take().expect("no stdout");
let mut output = Vec::new();
let _ = stdout.read_to_end(&mut output).await;
let _ = child.wait().await;
// Parse raw HTTP response
match parse_raw_http_response(&output) {
Ok(res) => Ok(res),
Err(e) => {
let mut resp = Response::new(Body::from(format!("Failed to parse response: {}", e)));
*resp.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
Ok(resp)
}
}
}
// Build raw HTTP request string
async fn build_raw_http_request(req: Request<Body>) -> Vec<u8> {
let mut raw = String::new();
let method = req.method();
let path = req.uri().path();
let version = match req.version() {
hyper::Version::HTTP_11 => "HTTP/1.1",
_ => "HTTP/1.1",
};
raw.push_str(&format!("{method} {path} {version}\r\n"));
for (key, value) in req.headers() {
if let Ok(val_str) = value.to_str() {
raw.push_str(&format!("{}: {}\r\n", key, val_str));
}
}
raw.push_str("\r\n");
let body_bytes = hyper::body::to_bytes(req.into_body()).await.unwrap_or_default();
let mut full = raw.into_bytes();
full.extend_from_slice(&body_bytes);
full
}
// Parse raw HTTP response into hyper::Response<Body>
fn parse_raw_http_response(bytes: &[u8]) -> Result<Response<Body>, Box<dyn std::error::Error>> {
let mut headers = [httparse::EMPTY_HEADER; 64];
let mut res = httparse::Response::new(&mut headers);
let parsed_len = res.parse(bytes)?.unwrap(); // returns offset
let status = res.code.unwrap_or(200);
let mut builder = Response::builder().status(status);
for h in res.headers.iter() {
builder = builder.header(h.name, std::str::from_utf8(h.value)?);
}
// Get body and append LiveReload script
let mut body = bytes[parsed_len..].to_vec();
let livereload_snippet = br#"<script>
const ws = new WebSocket("ws://localhost:35729");
ws.onmessage = (msg) => {
if (msg.data === "reload") location.reload();
};
</script>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
>
"#;
body.extend_from_slice(b"\n");
body.extend_from_slice(livereload_snippet);
Ok(builder.body(Body::from(body))?)
}
#[tokio::main]
async fn main() {
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
let make_svc = make_service_fn(|_conn| async {
Ok::<_, Infallible>(service_fn(handle_request))
});
println!("Proxy server running at http://{}", addr);
if let Err(e) = Server::bind(&addr).serve(make_svc).await {
eprintln!("server error: {}", e);
}
}

View File

@@ -0,0 +1,33 @@
use hyper::{Request, Body, Response, StatusCode};
use std::path::PathBuf;
// Import the rhai_tera module
extern crate rhai_tera;
pub fn get_calendars(_req: Request<Body>) -> Response<Body> {
let tera = setup_tera_with_rhai();
let context = tera::Context::new(); // no need to pass data — we're calling the Rhai function inside
let rendered = match tera.render("get_calendars.html.tera", &context) {
Ok(html) => Response::new(Body::from(html)),
Err(err) => {
// 🔍 Print the full error with source
eprintln!("Template render error: {:?}", err); // or use err.to_string() for cleaner message
let mut res = Response::new(Body::from(format!("Template error: {}", err)));
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
res
}
};
rendered
}
fn setup_tera_with_rhai() -> tera::Tera {
rhai_tera::setup_tera_with_rhai(
"src/templates/**/*",
"get_calendars",
PathBuf::from("../../database/scripts/calendars.rhai"),
"get_calendars"
)
}

View File

@@ -0,0 +1,72 @@
// Import the rhai_tera module from the root of the repo
extern crate rhai_tera;
mod controller {
pub mod calendar;
}
use controller::calendar;
use hyper::{Body, Method, Request, Response, StatusCode};
use std::convert::Infallible;
use std::io::{self, Read};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = read_request_from_stdin()?;
let request = parse_http_request(&input)?;
let response = handle_request(request).await?;
print_http_response(response).await;
Ok(())
}
fn read_request_from_stdin() -> io::Result<String> {
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
Ok(buffer)
}
fn parse_http_request(input: &str) -> Result<Request<Body>, Box<dyn std::error::Error>> {
let mut headers = [httparse::EMPTY_HEADER; 32];
let mut req = httparse::Request::new(&mut headers);
let bytes = input.as_bytes();
let parsed_len = req.parse(bytes)?.unwrap();
let method = req.method.ok_or("missing method")?.parse::<Method>()?;
let path = req.path.ok_or("missing path")?;
let uri = path.parse::<hyper::Uri>()?;
let body = &bytes[parsed_len..];
let request = Request::builder()
.method(method)
.uri(uri)
.body(Body::from(body.to_vec()))?;
Ok(request)
}
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
match (req.method(), req.uri().path()) {
(&Method::GET, "/calendars") => Ok(calendar::get_calendars(req)),
_ => {
let mut res = Response::new(Body::from("Not Found"));
*res.status_mut() = StatusCode::NOT_FOUND;
Ok(res)
}
}
}
async fn print_http_response(res: Response<Body>) {
println!("HTTP/1.1 {}", res.status());
for (key, value) in res.headers() {
println!("{}: {}", key, value.to_str().unwrap_or_default());
}
println!(); // blank line before body
let body_bytes = hyper::body::to_bytes(res.into_body()).await.unwrap();
let body = String::from_utf8_lossy(&body_bytes);
println!("{}", body);
}

View File

@@ -0,0 +1,17 @@
<h2>Calendars</h2>
<ul>
{% for cal in get_calendars() %}
<li>
<article>
<header>
<h3>{{ cal.name }}</h3>
<p>{{ cal.description }}</p>
</header>
<footer>
<p>{{ cal.id }}</p>
<p>{{ cal.owner_id }}</p>
</footer>
</article>
</li>
{% endfor %}
</ul>