updated README and a bunch of other stuff
This commit is contained in:
29
parts/ppc.server/Cargo.toml
Normal file
29
parts/ppc.server/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
cargo-features = ["profile-rustflags"]
|
||||
|
||||
[package]
|
||||
name = "mize-part-ppc-server"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
name = "mize_part_ppc_server"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mize = { workspace = true }
|
||||
axum = "0.8.9"
|
||||
axum-oidc = "=1.0.0-dev-1"
|
||||
openidconnect = { version = "4", features = ["reqwest", "rustls-tls"], default-features = false }
|
||||
tower = "0.5.3"
|
||||
tower-sessions = "0.15.0"
|
||||
tower-http = { version = "0.7.1", features = ["cors", "trace"] }
|
||||
serde_json = "1.0"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "net", "macros"] }
|
||||
tracing = "0.1"
|
||||
dioxus = { version = "0.7", default-features = false, features = ["ssr", "macro", "html", "signals"] }
|
||||
|
||||
[profile.dev]
|
||||
rustflags = ["-C", "prefer-dynamic", "-C", "link-args=-Wl,--export-dynamic"]
|
||||
|
||||
[profile.release]
|
||||
rustflags = ["-C", "prefer-dynamic"]
|
||||
176
parts/ppc.server/src/lib.rs
Normal file
176
parts/ppc.server/src/lib.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
mod pages;
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::thread;
|
||||
|
||||
use axum::{
|
||||
Json, Router,
|
||||
error_handling::HandleErrorLayer,
|
||||
http::Uri,
|
||||
response::IntoResponse,
|
||||
routing::{any, get},
|
||||
};
|
||||
use axum_oidc::handle_oidc_redirect;
|
||||
use axum_oidc::{
|
||||
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer,
|
||||
OidcRpInitiatedLogout, error::MiddlewareError,
|
||||
};
|
||||
use mize::prelude::*;
|
||||
use openidconnect::Scope;
|
||||
use openidconnect::{ClientId, ClientSecret, IssuerUrl};
|
||||
use serde_json::{Value, json};
|
||||
use tokio::runtime::Runtime;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_sessions::{
|
||||
Expiry, MemoryStore, SessionManagerLayer,
|
||||
cookie::{SameSite, time::Duration},
|
||||
};
|
||||
|
||||
fn ensure_default(rel: &str, default: ItemData) -> MizeResult<()> {
|
||||
let mut mize = get_global_mize()?;
|
||||
let cur = mize
|
||||
.cur_eval_part()?
|
||||
.ok_or_else(|| mize_err!("no cur_eval_part"))?;
|
||||
let full = format!("{}.{}", cur.dot(), rel);
|
||||
if mize.get_config(&full).is_ok() {
|
||||
return Ok(());
|
||||
}
|
||||
let segs: Vec<&str> = rel.split('.').filter(|s| !s.is_empty()).collect();
|
||||
let last = *segs.last().ok_or_else(|| mize_err!("empty opt path"))?;
|
||||
let mut parent = cur.clone();
|
||||
for seg in &segs[..segs.len() - 1] {
|
||||
parent = parent.join(seg)?;
|
||||
}
|
||||
let original = Some(cur);
|
||||
mize.set_cur_eval_part(Some(parent))?;
|
||||
opt(last).default_val(default);
|
||||
mize.set_cur_eval_part(original)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn part_ppc_server() -> MizeResult<()> {
|
||||
ensure_default("url", "http://localhost:3000".into_item_data())?;
|
||||
ensure_default("bind", "0.0.0.0:8010".into_item_data())?;
|
||||
|
||||
thread::spawn(|| {
|
||||
let rt = match Runtime::new() {
|
||||
Ok(rt) => rt,
|
||||
Err(e) => {
|
||||
eprintln!("ppc.server: failed to create runtime: {e}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
if let Err(e) = rt.block_on(start_server()) {
|
||||
e.log();
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn start_server() -> MizeResult<()> {
|
||||
let mut mize = get_global_mize()?;
|
||||
|
||||
let issuer = mize.get_config("auth.server.issuer")?.value_string()?;
|
||||
let client_id = mize.get_config("auth.server.client_id")?.value_string()?;
|
||||
let client_secret = mize.get_config("auth.server.client_secret")?.value_string()?;
|
||||
let url = mize.get_config("ppc.server.url")?.value_string()?;
|
||||
let bind = mize.get_config("ppc.server.bind")?.value_string()?;
|
||||
let redirect_url = format!("{url}/oidc");
|
||||
|
||||
let session_store = MemoryStore::default();
|
||||
let session_layer = SessionManagerLayer::new(session_store)
|
||||
.with_secure(false)
|
||||
.with_same_site(SameSite::Lax)
|
||||
.with_expiry(Expiry::OnInactivity(Duration::seconds(120)));
|
||||
|
||||
let oidc_login_service = ServiceBuilder::new()
|
||||
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
||||
dbg!(&e);
|
||||
e.into_response()
|
||||
}))
|
||||
.layer(OidcLoginLayer::<EmptyAdditionalClaims>::new());
|
||||
|
||||
let oidc_client = OidcClient::<EmptyAdditionalClaims>::builder()
|
||||
.with_default_http_client()
|
||||
.with_redirect_url(Uri::from_str(redirect_url.as_str())?)
|
||||
.with_client_id(ClientId::new(client_id))
|
||||
.add_scope(Scope::new("profile".into()))
|
||||
.add_scope(Scope::new("email".into()))
|
||||
.with_client_secret(ClientSecret::new(client_secret))
|
||||
.discover(IssuerUrl::new(issuer).expect("Invalid IssuerUrl"))
|
||||
.await
|
||||
.map_err(|e| mize_err!("oidc discover failed: {:?}", e))?
|
||||
.build();
|
||||
|
||||
let oidc_auth_service = ServiceBuilder::new()
|
||||
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
||||
dbg!(&e);
|
||||
e.into_response()
|
||||
}))
|
||||
.layer(OidcAuthLayer::new(oidc_client));
|
||||
|
||||
let app = Router::new()
|
||||
.route("/foo", get(authenticated))
|
||||
.route("/logout", get(logout))
|
||||
.layer(oidc_login_service)
|
||||
.route("/bar", get(maybe_authenticated))
|
||||
.route("/oidc", any(handle_oidc_redirect::<EmptyAdditionalClaims>))
|
||||
.layer(oidc_auth_service)
|
||||
.layer(session_layer)
|
||||
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||
.route("/", get(pages::main_page()))
|
||||
.route("/mc", get(pages::mc_page()))
|
||||
.route(
|
||||
"/.well-known/matrix/client",
|
||||
get(handle_matrix_client).layer(tower_http::cors::CorsLayer::permissive()),
|
||||
)
|
||||
.route("/.well-known/matrix/server", get(handle_matrix_server));
|
||||
|
||||
tracing::info!("ppc.server listening on http://{bind}");
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(&bind)
|
||||
.await
|
||||
.map_err(|e| mize_err!("bind {bind}: {e}"))?;
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await
|
||||
.map_err(|e| mize_err!("axum serve: {e}"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn authenticated(claims: OidcClaims<EmptyAdditionalClaims>) -> impl IntoResponse {
|
||||
format!("Hello {}", claims.subject().as_str())
|
||||
}
|
||||
|
||||
async fn maybe_authenticated(
|
||||
claims: Result<OidcClaims<EmptyAdditionalClaims>, axum_oidc::error::ExtractorError>,
|
||||
) -> impl IntoResponse {
|
||||
if let Ok(claims) = claims {
|
||||
format!(
|
||||
"Hello {}! You are already logged in from another Handler.",
|
||||
claims.subject().as_str()
|
||||
)
|
||||
} else {
|
||||
"Hello anon!".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
async fn logout(logout: OidcRpInitiatedLogout) -> impl IntoResponse {
|
||||
logout.with_post_logout_redirect(Uri::from_static("https://localhost:3000"))
|
||||
}
|
||||
|
||||
async fn handle_matrix_client() -> Json<Value> {
|
||||
Json(json!({
|
||||
"m.homeserver": {
|
||||
"base_url": "https://matrix.ppc.social"
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
async fn handle_matrix_server() -> Json<Value> {
|
||||
Json(json!({
|
||||
"m.server": "matrix.ppc.social:443"
|
||||
}))
|
||||
}
|
||||
186
parts/ppc.server/src/pages.rs
Normal file
186
parts/ppc.server/src/pages.rs
Normal file
@@ -0,0 +1,186 @@
|
||||
use axum::response::Html;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const SITE_CSS: &str = r#"
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html { color-scheme: light dark; }
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0 1.25rem 2.5rem;
|
||||
max-width: 60ch;
|
||||
margin-inline: auto;
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.6;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background-color: #F7F8F4;
|
||||
color: #20251C;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
h1, h2 {
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
color: #20251C;
|
||||
}
|
||||
h1 { margin: 2.5rem 0 1.5rem; font-size: 1.75rem; }
|
||||
h2 { margin: 2.5rem 0 1rem; font-size: 1.35rem; }
|
||||
p { margin: 0.85rem 0; }
|
||||
.tagline { color: #66705E; font-style: italic; }
|
||||
.meta { color: #66705E; font-size: 0.95rem; }
|
||||
a {
|
||||
color: #376777;
|
||||
text-decoration-thickness: 1px;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
a:hover { color: #79500F; }
|
||||
.accent { color: #79500F; }
|
||||
.accent-bar {
|
||||
display: inline-block;
|
||||
width: 2.5rem;
|
||||
height: 0.35rem;
|
||||
background: #FEC251;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body { background-color: #171D18; color: #F2F4EA; }
|
||||
h1, h2 { color: #F2F4EA; }
|
||||
.tagline, .meta { color: #AAB5A4; }
|
||||
a { color: #B9E4EB; }
|
||||
a:hover { color: #D7E47D; }
|
||||
.accent { color: #D7E47D; }
|
||||
.accent-bar { background: #D7E47D; }
|
||||
}
|
||||
"#;
|
||||
|
||||
const PLAUSIBLE_SRC: &str = "https://plausible.c2vi.dev/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js";
|
||||
const PLAUSIBLE_BOOT: &str =
|
||||
"window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }";
|
||||
|
||||
fn render_page(title: &str, body: Element) -> Html<String> {
|
||||
let markup = dioxus::ssr::render_element(rsx! {
|
||||
head {
|
||||
meta { charset: "UTF-8" }
|
||||
meta { name: "viewport", content: "width=device-width, initial-scale=1.0" }
|
||||
title { "{title}" }
|
||||
script {
|
||||
defer: true,
|
||||
"data-domain": "ppc.social",
|
||||
src: PLAUSIBLE_SRC,
|
||||
}
|
||||
script { dangerous_inner_html: PLAUSIBLE_BOOT }
|
||||
style { {SITE_CSS} }
|
||||
}
|
||||
body { {body} }
|
||||
});
|
||||
Html(format!("<!DOCTYPE html>\n<html lang=\"en\">\n{markup}\n</html>"))
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn MainPageBody() -> Element {
|
||||
rsx! {
|
||||
div { class: "accent-bar" }
|
||||
h1 { "People Corner" }
|
||||
p { class: "tagline", "\"Cool People doing Cool things together\"" }
|
||||
p {
|
||||
"Read what this is all about: "
|
||||
a { href: "https://wiki.ppc.social/u/c2vi/The-Idea-of-PPC", "About" }
|
||||
}
|
||||
p {
|
||||
"There is a Discord Server: "
|
||||
a { href: "https://discord.gg/cMKQNJMkRe", "https://discord.gg/cMKQNJMkRe" }
|
||||
}
|
||||
p {
|
||||
"There is a "
|
||||
a { href: "https://ppc.social/mc", "Minecraft server" }
|
||||
"."
|
||||
}
|
||||
p {
|
||||
"There is "
|
||||
a { href: "https://wiki.ppc.social", "The PPC Wiki Projekt" }
|
||||
"."
|
||||
}
|
||||
p {
|
||||
"There is an "
|
||||
a { href: "https://www.instagram.com/ppc.social/", "Instagram" }
|
||||
" and a "
|
||||
a { href: "https://www.youtube.com/@ppc.social", "Youtube Channel" }
|
||||
}
|
||||
p { class: "meta", "and that's about all for now..." }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn McPageBody() -> Element {
|
||||
rsx! {
|
||||
div { class: "accent-bar" }
|
||||
h1 { "Welcome to the PPC Minecraft Cluster" }
|
||||
h2 { "Address" }
|
||||
p { class: "accent", "first.ppc.social" }
|
||||
p { class: "meta", "Java 1.21.11 and Bedrock (bedrock not working rn)" }
|
||||
h2 { "Discord" }
|
||||
p {
|
||||
a {
|
||||
href: "https://discord.gg/cMKQNJMkRe",
|
||||
target: "_blank",
|
||||
"https://discord.gg/cMKQNJMkRe"
|
||||
}
|
||||
}
|
||||
p { class: "meta", "come and say hi!" }
|
||||
p { "See you ingame and invite all your friends to play as well!!" }
|
||||
h2 { "SimpleVoicechat" }
|
||||
p {
|
||||
"This is "
|
||||
span { class: "accent", "not required" }
|
||||
", but makes playing soooo much more fun, so it's "
|
||||
span { class: "accent", "highly encouraged" }
|
||||
"."
|
||||
}
|
||||
p {
|
||||
"Instructions can be found here:"
|
||||
}
|
||||
p {
|
||||
a { href: "https://fabricmc.net/use/installer/", "Fabric" }
|
||||
}
|
||||
p {
|
||||
a {
|
||||
href: "https://modrepo.de/minecraft/voicechat/wiki/installation#fabric",
|
||||
"SimpleVoiceChat"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main_page() -> Html<String> {
|
||||
render_page("People Corner", rsx! { MainPageBody {} })
|
||||
}
|
||||
|
||||
pub fn mc_page() -> Html<String> {
|
||||
render_page("PPC Minecraft Server", rsx! { McPageBody {} })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn main_page_renders() {
|
||||
let html = main_page().0;
|
||||
assert!(html.contains("<!DOCTYPE html>"));
|
||||
assert!(html.contains("People Corner"));
|
||||
assert!(html.contains("Cool People doing Cool things together"));
|
||||
assert!(html.contains("#F7F8F4"));
|
||||
assert!(html.contains("#376777"));
|
||||
assert!(html.contains("https://wiki.ppc.social/u/c2vi/The-Idea-of-PPC"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mc_page_renders() {
|
||||
let html = mc_page().0;
|
||||
assert!(html.contains("PPC Minecraft Cluster"));
|
||||
assert!(html.contains("first.ppc.social"));
|
||||
assert!(html.contains("SimpleVoicechat"));
|
||||
assert!(html.contains("https://fabricmc.net/use/installer/"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user