add small frontend stack w/ tailwind & askama
This commit is contained in:
68
Cargo.lock
generated
68
Cargo.lock
generated
@@ -45,6 +45,7 @@ dependencies = [
|
||||
name = "arche"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"askama",
|
||||
"axum",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
@@ -52,6 +53,7 @@ dependencies = [
|
||||
"rand 0.9.1",
|
||||
"serenity",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
@@ -65,6 +67,48 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4"
|
||||
dependencies = [
|
||||
"askama_derive",
|
||||
"itoa",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_derive"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "129397200fe83088e8a68407a8e2b1f826cf0086b21ccdb866a722c8bcd3a94f"
|
||||
dependencies = [
|
||||
"askama_parser",
|
||||
"basic-toml",
|
||||
"memchr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "askama_parser"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6ab5630b3d5eaf232620167977f95eb51f3432fc76852328774afbd242d4358"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.88"
|
||||
@@ -163,6 +207,15 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "basic-toml"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@@ -1427,6 +1480,12 @@ version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.5"
|
||||
@@ -2568,6 +2627,15 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
|
||||
@@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
askama = "0.14.0"
|
||||
axum = "0.8.3"
|
||||
chrono = "0.4.41"
|
||||
chrono-tz = "0.10.3"
|
||||
@@ -11,5 +12,6 @@ dotenvy = "0.15.7"
|
||||
rand = "0.9.1"
|
||||
serenity = "0.12.4"
|
||||
tokio = { version = "1.44.2", features = ["full"] }
|
||||
tower = "0.5.2"
|
||||
tracing = "0.1.41"
|
||||
tracing-subscriber = "0.3.20"
|
||||
|
||||
2
askama.toml
Normal file
2
askama.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[general]
|
||||
dirs = ["web"]
|
||||
92
build.rs
Normal file
92
build.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=web");
|
||||
|
||||
let os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_else(|_| String::from("unknown"));
|
||||
let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_else(|_| String::from("unknown"));
|
||||
let download_url = match (os.as_str(), arch.as_str()) {
|
||||
("macos", "aarch64") => {
|
||||
"https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64"
|
||||
}
|
||||
("linux", "x86_64") => {
|
||||
"https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64"
|
||||
}
|
||||
("linux", "aarch64") => {
|
||||
"https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-arm64"
|
||||
}
|
||||
_ => return Err(format!("Unsupported platform: {} {}", os, arch).into()),
|
||||
};
|
||||
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR")?);
|
||||
let tailwind_binary = out_dir.join("tailwind");
|
||||
fs::create_dir_all(&out_dir)?;
|
||||
|
||||
download_tailwind(&download_url, &tailwind_binary)?;
|
||||
|
||||
println!("cargo:rustc-env=TAILWIND_BIN={}", tailwind_binary.display());
|
||||
|
||||
run_tailwind(&tailwind_binary)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_tailwind(bin: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Building CSS with Tailwind...");
|
||||
|
||||
let basedir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into());
|
||||
let input = Path::new(&basedir).join("web").join("input.css");
|
||||
let inputstr = input.to_str().unwrap();
|
||||
let output = Path::new(&basedir).join("web").join("styles.css");
|
||||
let outputstr = output.to_str().unwrap();
|
||||
let args = vec!["-i", inputstr, "-o", outputstr, "--minify"];
|
||||
|
||||
let run = Command::new(&bin).args(args).status()?;
|
||||
match run.success() {
|
||||
true => println!("Tailwind CSS build complete."),
|
||||
false => println!("Tailwind CSS build failed."),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn download_tailwind(url: &str, target_path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if target_path.exists() {
|
||||
return Ok(()); // if present, assume the file is the appropriate tailwind binary
|
||||
}
|
||||
|
||||
let tmp_download = target_path.with_extension("tmp");
|
||||
let download_status = if Command::new("curl").arg("--version").output().is_ok() {
|
||||
Command::new("curl")
|
||||
.arg("-L") // Follow redirects
|
||||
.arg("-o")
|
||||
.arg(&tmp_download)
|
||||
.arg(url)
|
||||
.status()?
|
||||
} else if Command::new("wget").arg("--version").output().is_ok() {
|
||||
Command::new("wget")
|
||||
.arg("-O")
|
||||
.arg(&tmp_download)
|
||||
.arg(url)
|
||||
.status()?
|
||||
} else {
|
||||
return Err("Neither curl nor wget is available".into());
|
||||
};
|
||||
|
||||
if !download_status.success() {
|
||||
return Err(format!("Tailwind binary download failed: {}", download_status).into());
|
||||
}
|
||||
fs::rename(&tmp_download, target_path)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = fs::metadata(target_path)?.permissions();
|
||||
perms.set_mode(0o777);
|
||||
fs::set_permissions(target_path, perms)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -4,6 +4,7 @@ use tokio::net::TcpListener;
|
||||
mod discordbot;
|
||||
mod router;
|
||||
mod setup;
|
||||
mod website;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
use axum::{Router, http::StatusCode, routing::get};
|
||||
use chrono::{NaiveDate, Utc};
|
||||
use tower::service_fn;
|
||||
|
||||
use crate::router::redirects::redirects;
|
||||
use crate::{router::redirects::redirects, website::website_service};
|
||||
|
||||
mod redirects;
|
||||
|
||||
pub fn init() -> Router {
|
||||
Router::new().merge(redirects()).nest("/api/", api())
|
||||
Router::new()
|
||||
.merge(redirects())
|
||||
.nest("/api/", api())
|
||||
.fallback_service(service_fn(website_service))
|
||||
}
|
||||
|
||||
fn api() -> Router {
|
||||
|
||||
23
src/website/mod.rs
Normal file
23
src/website/mod.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
use axum::{
|
||||
body::Body,
|
||||
http::{Request, StatusCode, header},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
|
||||
use crate::website::pages::index::page_index;
|
||||
|
||||
mod pages;
|
||||
|
||||
const STYLES_CSS: &str = include_str!("../../web/styles.css");
|
||||
|
||||
pub async fn website_service(req: Request<Body>) -> Result<Response, Infallible> {
|
||||
let path = req.uri().path().trim_start_matches("/");
|
||||
|
||||
Ok(match path {
|
||||
"" | "index" | "index.html" | "index.htm" => page_index().await,
|
||||
"styles.css" => ([(header::CONTENT_TYPE, "text/css")], STYLES_CSS).into_response(),
|
||||
_ => StatusCode::NOT_FOUND.into_response(),
|
||||
})
|
||||
}
|
||||
19
src/website/pages/index.rs
Normal file
19
src/website/pages/index.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use askama::Template;
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{Html, IntoResponse, Response},
|
||||
};
|
||||
|
||||
use crate::website::pages::INTERNAL_SERVER_ERROR_MSG;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct PageIndex;
|
||||
|
||||
pub async fn page_index() -> Response {
|
||||
let a = PageIndex;
|
||||
match a.render() {
|
||||
Ok(res) => (StatusCode::OK, Html(res)).into_response(),
|
||||
Err(_e) => (StatusCode::INTERNAL_SERVER_ERROR, INTERNAL_SERVER_ERROR_MSG).into_response(),
|
||||
}
|
||||
}
|
||||
4
src/website/pages/mod.rs
Normal file
4
src/website/pages/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
pub mod index;
|
||||
|
||||
const INTERNAL_SERVER_ERROR_MSG: &str =
|
||||
"An internal server error occured while rendering your HTML. Sorry!";
|
||||
16
web/index.html
Normal file
16
web/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!doctype html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>gractwo.pl | Witamy!</title>
|
||||
<link href="styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body class="min-h-screen text-center flex items-center justify-center">
|
||||
<p>
|
||||
Witamy na gractwo.pl! Zapraszamy na nasz
|
||||
<a href="/discord" class="underline text-blue-400">serwer Discord</a
|
||||
>!
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
1
web/input.css
Normal file
1
web/input.css
Normal file
@@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
||||
2
web/styles.css
Normal file
2
web/styles.css
Normal file
@@ -0,0 +1,2 @@
|
||||
/*! tailwindcss v4.1.12 | MIT License | https://tailwindcss.com */
|
||||
@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-blue-400:oklch(70.7% .165 254.624);--color-purple-400:oklch(71.4% .203 305.504);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.static{position:static}.flex{display:flex}.min-h-screen{min-height:100vh}.items-center{align-items:center}.justify-center{justify-content:center}.text-center{text-align:center}.text-blue-400{color:var(--color-blue-400)}.underline{text-decoration-line:underline}}
|
||||
Reference in New Issue
Block a user