Some minor restructuring and improvements

This commit is contained in:
Lol3rrr
2024-09-08 00:41:12 +02:00
parent ae6c1b590f
commit 828df3290a
17 changed files with 387 additions and 106 deletions

96
Cargo.lock generated
View File

@@ -152,6 +152,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum", "axum",
"common",
"diesel", "diesel",
"diesel-async", "diesel-async",
"diesel_async_migrations", "diesel_async_migrations",
@@ -287,6 +288,13 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271"
[[package]]
name = "common"
version = "0.1.0"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "config" name = "config"
version = "0.14.0" version = "0.14.0"
@@ -631,8 +639,11 @@ dependencies = [
name = "frontend" name = "frontend"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"common",
"leptos", "leptos",
"leptos_router",
"reqwasm", "reqwasm",
"stylers",
] ]
[[package]] [[package]]
@@ -1235,6 +1246,32 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "leptos_router"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5006e35b7c768905286dbea0d3525396cd39d961cb7b9fb664aa00b0c984ae6"
dependencies = [
"cfg-if",
"gloo-net 0.6.0",
"itertools",
"js-sys",
"lazy_static",
"leptos",
"linear-map",
"once_cell",
"percent-encoding",
"send_wrapper",
"serde",
"serde_json",
"serde_qs 0.13.0",
"thiserror",
"tracing",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "leptos_server" name = "leptos_server"
version = "0.6.14" version = "0.6.14"
@@ -1251,18 +1288,43 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "levenshtein"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.158" version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "linear-map"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee"
dependencies = [
"serde",
"serde_test",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "litrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
dependencies = [
"proc-macro2",
]
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.12" version = "0.4.12"
@@ -2071,6 +2133,17 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "serde_qs"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
dependencies = [
"percent-encoding",
"serde",
"thiserror",
]
[[package]] [[package]]
name = "serde_spanned" name = "serde_spanned"
version = "0.6.7" version = "0.6.7"
@@ -2080,6 +2153,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_test"
version = "1.0.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@@ -2110,7 +2192,7 @@ dependencies = [
"send_wrapper", "send_wrapper",
"serde", "serde",
"serde_json", "serde_json",
"serde_qs", "serde_qs 0.12.0",
"server_fn_macro_default", "server_fn_macro_default",
"thiserror", "thiserror",
"url", "url",
@@ -2249,6 +2331,18 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "stylers"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03e306edf4b3cb5cff4b2e21b8895ed9e70fbd1bbb3a8dfb7e4cc245a763a955"
dependencies = [
"levenshtein",
"litrs",
"proc-macro2",
"quote",
]
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.6.1" version = "2.6.1"

View File

@@ -1,3 +1,3 @@
[workspace] [workspace]
members = ["backend", "frontend"] members = ["backend", "common", "frontend"]
resolver = "2" resolver = "2"

View File

@@ -20,3 +20,5 @@ diesel = { version = "2.2", features = ["serde_json"] }
diesel-async = { version = "0.5", features = ["postgres"] } diesel-async = { version = "0.5", features = ["postgres"] }
serde_json = "1.0.128" serde_json = "1.0.128"
diesel_async_migrations = { version = "0.15" } diesel_async_migrations = { version = "0.15" }
common = { path = "../common/" }

115
backend/src/api.rs Normal file
View File

@@ -0,0 +1,115 @@
pub mod demos {
use crate::UserSession;
use diesel_async::RunQueryDsl;
use diesel::prelude::*;
use axum::extract::{State, Path};
use std::sync::Arc;
struct DemoState {
upload_folder: std::path::PathBuf,
}
pub fn router<P>(upload_folder: P) -> axum::Router where P: Into<std::path::PathBuf> {
axum::Router::new()
.route("/list", axum::routing::get(list))
.route("/upload", axum::routing::post(upload).layer(axum::extract::DefaultBodyLimit::max(500*1024*1024)))
.route("/:id/info", axum::routing::get(info))
.with_state(Arc::new(DemoState {
upload_folder: upload_folder.into(),
}))
}
async fn list(session: UserSession) -> Result<axum::response::Json<Vec<common::BaseDemoInfo>>, axum::http::StatusCode> {
let steam_id = session.data().steam_id.ok_or_else(|| axum::http::StatusCode::UNAUTHORIZED)?;
tracing::info!("SteamID: {:?}", steam_id);
let query = crate::schema::demos::dsl::demos.filter(crate::schema::demos::dsl::steam_id.eq(steam_id as i64));
let results: Vec<crate::models::Demo> = query.load(&mut crate::db_connection().await).await.unwrap();
Ok(axum::response::Json(results.into_iter().map(|demo| common::BaseDemoInfo {
id: demo.demo_id,
}).collect::<Vec<_>>()))
}
async fn upload(State(state): State<Arc<DemoState>>, session: crate::UserSession, form: axum::extract::Multipart) -> Result<axum::response::Redirect, (axum::http::StatusCode, &'static str)> {
let steam_id = session.data().steam_id.ok_or_else(|| (axum::http::StatusCode::UNAUTHORIZED, "Not logged in"))?;
tracing::info!("Upload for Session: {:?}", steam_id);
let file_content = crate::get_demo_from_upload("demo", form).await.unwrap();
let user_folder = std::path::Path::new(&state.upload_folder).join(format!("{}/", steam_id));
if !tokio::fs::try_exists(&user_folder).await.unwrap_or(false) {
tokio::fs::create_dir_all(&user_folder).await.unwrap();
}
let timestamp_secs = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs();
let demo_file_path = user_folder.join(format!("{}.dem", timestamp_secs));
tokio::fs::write(demo_file_path, file_content).await.unwrap();
let query = diesel::dsl::insert_into(crate::schema::demos::dsl::demos).values(crate::models::Demo {
demo_id: timestamp_secs as i64,
steam_id: steam_id as i64,
});
query.execute(&mut crate::db_connection().await).await.unwrap();
Ok(axum::response::Redirect::to("/"))
}
async fn info(session: UserSession, Path(demo_id): Path<i64>) -> Result<(), axum::http::StatusCode> {
tracing::info!("Get info for Demo: {:?}", demo_id);
Ok(())
}
}
pub mod steam {
use axum::extract::State;
use std::sync::Arc;
pub fn router(url: &str, callback_path: &str) -> axum::Router {
axum::Router::new()
.route("/login", axum::routing::get(steam_login))
.route("/callback", axum::routing::get(steam_callback))
.with_state(Arc::new(steam_openid::SteamOpenId::new(url, callback_path).unwrap()))
}
async fn steam_login(State(openid): State<Arc<steam_openid::SteamOpenId>>) -> Result<axum::response::Redirect, axum::http::StatusCode> {
let url = openid.get_redirect_url();
Ok(axum::response::Redirect::to(url))
}
async fn steam_callback(
State(openid): State<Arc<steam_openid::SteamOpenId>>,
mut session: crate::UserSession,
request: axum::extract::Request,
) -> Result<axum::response::Redirect, axum::http::StatusCode> {
tracing::info!("Steam Callback");
let query = request.uri().query().ok_or_else(|| {
tracing::error!("Missing query in parameters");
axum::http::StatusCode::BAD_REQUEST
})?;
let id = openid.verify(query).await.map_err(|e| {
tracing::error!("Verifying OpenID: {:?}", e);
axum::http::StatusCode::BAD_REQUEST
})?;
session
.modify_data(|data| {
data.steam_id = Some(id);
})
.await;
Ok(axum::response::Redirect::to("/"))
}
}
pub fn router() -> axum::Router {
axum::Router::new()
.nest("/steam/", steam::router("http://192.168.0.156:3000", "/api/steam/callback"))
.nest("/demos/", demos::router("uploads/"))
}

View File

@@ -1,5 +1,3 @@
use serde::{Deserialize, Serialize};
pub mod models; pub mod models;
pub mod schema; pub mod schema;
@@ -34,3 +32,5 @@ pub async fn get_demo_from_upload(name: &str, mut form: axum::extract::Multipart
None None
} }
pub mod api;

View File

@@ -1,11 +1,8 @@
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
use diesel::prelude::*; use diesel::prelude::*;
use diesel_async::{RunQueryDsl, AsyncConnection, AsyncPgConnection}; use diesel_async::RunQueryDsl;
static OPENID: std::sync::LazyLock<steam_openid::SteamOpenId> = std::sync::LazyLock::new(|| {
steam_openid::SteamOpenId::new("http://192.168.0.156:3000", "/api/steam/callback").unwrap()
});
static UPLOAD_FOLDER: &str = "uploads/"; static UPLOAD_FOLDER: &str = "uploads/";
const MIGRATIONS: diesel_async_migrations::EmbeddedMigrations = diesel_async_migrations::embed_migrations!("../migrations/"); const MIGRATIONS: diesel_async_migrations::EmbeddedMigrations = diesel_async_migrations::embed_migrations!("../migrations/");
@@ -33,7 +30,7 @@ async fn main() {
let session_layer = tower_sessions::SessionManagerLayer::new(session_store) let session_layer = tower_sessions::SessionManagerLayer::new(session_store)
.with_secure(false) .with_secure(false)
.with_expiry(tower_sessions::Expiry::OnInactivity( .with_expiry(tower_sessions::Expiry::OnInactivity(
time::Duration::minutes(15), time::Duration::hours(48),
)); ));
if !tokio::fs::try_exists(UPLOAD_FOLDER).await.unwrap_or(false) { if !tokio::fs::try_exists(UPLOAD_FOLDER).await.unwrap_or(false) {
@@ -41,14 +38,7 @@ async fn main() {
} }
let router = axum::Router::new() let router = axum::Router::new()
.nest_service( .nest("/api/", backend::api::router())
"/api/",
axum::Router::new()
.route("/steam/callback", axum::routing::get(steam_callback))
.route("/steam/login", axum::routing::get(steam_login))
.route("/demos/upload", axum::routing::post(upload).layer(axum::extract::DefaultBodyLimit::max(1024*1024*500)))
.route("/demos/list", axum::routing::get(demos_list))
)
.layer(session_layer) .layer(session_layer)
.nest_service("/", tower_http::services::ServeDir::new("frontend/dist/")); .nest_service("/", tower_http::services::ServeDir::new("frontend/dist/"));
@@ -56,71 +46,6 @@ async fn main() {
axum::serve(listener, router).await.unwrap(); axum::serve(listener, router).await.unwrap();
} }
async fn upload(session: backend::UserSession, form: axum::extract::Multipart) -> Result<axum::response::Redirect, (axum::http::StatusCode, &'static str)> { async fn demo_info(session: backend::UserSession) -> Result<(), axum::http::StatusCode> {
let steam_id = session.data().steam_id.ok_or_else(|| (axum::http::StatusCode::UNAUTHORIZED, "Not logged in"))?;
tracing::info!("Upload for Session: {:?}", steam_id);
let file_content = backend::get_demo_from_upload("demo", form).await.unwrap();
let user_folder = std::path::Path::new(UPLOAD_FOLDER).join(format!("{}/", steam_id));
if !tokio::fs::try_exists(&user_folder).await.unwrap_or(false) {
tokio::fs::create_dir_all(&user_folder).await.unwrap();
}
let timestamp_secs = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).unwrap().as_secs();
let demo_file_path = user_folder.join(format!("{}.dem", timestamp_secs));
tokio::fs::write(demo_file_path, file_content).await.unwrap();
let query = diesel::dsl::insert_into(backend::schema::demos::dsl::demos).values(backend::models::Demo {
demo_id: timestamp_secs as i64,
steam_id: steam_id as i64,
});
query.execute(&mut backend::db_connection().await).await.unwrap();
Ok(axum::response::Redirect::to("/"))
}
async fn steam_login() -> Result<axum::response::Redirect, axum::http::StatusCode> {
let url = OPENID.get_redirect_url();
Ok(axum::response::Redirect::to(url))
}
async fn steam_callback(
mut session: backend::UserSession,
request: axum::extract::Request,
) -> Result<axum::response::Redirect, axum::http::StatusCode> {
tracing::info!("Steam Callback");
let query = request.uri().query().ok_or_else(|| {
tracing::error!("Missing query in parameters");
axum::http::StatusCode::BAD_REQUEST
})?;
let id = OPENID.verify(query).await.map_err(|e| {
tracing::error!("Verifying OpenID: {:?}", e);
axum::http::StatusCode::BAD_REQUEST
})?;
session
.modify_data(|data| {
data.steam_id = Some(id);
})
.await;
Ok(axum::response::Redirect::to("/"))
}
async fn demos_list(session: backend::UserSession) -> Result<(), axum::http::StatusCode> {
let steam_id = session.data().steam_id.ok_or_else(|| axum::http::StatusCode::UNAUTHORIZED)?;
tracing::info!("SteamID: {:?}", steam_id);
let query = backend::schema::demos::dsl::demos.filter(backend::schema::demos::dsl::steam_id.eq(steam_id as i64));
let results: Vec<backend::models::Demo> = query.load(&mut backend::db_connection().await).await.unwrap();
dbg!(&results);
Ok(()) Ok(())
} }

View File

@@ -1,6 +1,3 @@
use diesel::prelude::*;
use diesel_async::RunQueryDsl;
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct UserSessionData { pub struct UserSessionData {
pub steam_id: Option<u64>, pub steam_id: Option<u64>,

7
common/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "common"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1", features = ["derive"] }

4
common/src/lib.rs Normal file
View File

@@ -0,0 +1,4 @@
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct BaseDemoInfo {
pub id: i64,
}

View File

@@ -5,4 +5,8 @@ edition = "2021"
[dependencies] [dependencies]
leptos = { version = "0.6", features = ["csr", "nightly"] } leptos = { version = "0.6", features = ["csr", "nightly"] }
leptos_router = { version = "0.6", features = ["csr"] }
reqwasm = "0.5.0" reqwasm = "0.5.0"
stylers = { version = "0.3" }
common = { path = "../common/" }

4
frontend/Trunk.toml Normal file
View File

@@ -0,0 +1,4 @@
[[hooks]]
stage = "post_build"
command = "sh"
command_arguments = ["-c", "cp ../target/stylers/main.css $TRUNK_STAGING_DIR/"]

View File

@@ -1,5 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head></head> <head>
<link rel="stylesheet" href="/main.css">
</head>
<body></body> <body></body>
</html> </html>

17
frontend/src/demo.rs Normal file
View File

@@ -0,0 +1,17 @@
use leptos::*;
#[leptos::component]
pub fn demo() -> impl leptos::IntoView {
let params = leptos_router::use_params_map();
let id = move || params.with(|params| params.get("id").cloned().unwrap_or_default());
let demo_info = create_resource(|| (), move |_| async move {
let res = reqwasm::http::Request::get(&format!("/api/demos/{}/info", id())).send().await.unwrap();
dbg!(res.text().await);
0
});
view! {
<h2>Demo - {id}</h2>
}
}

107
frontend/src/lib.rs Normal file
View File

@@ -0,0 +1,107 @@
use leptos::*;
use leptos_router::A;
mod demo;
pub use demo::Demo;
#[leptos::component]
pub fn demo_list_entry(demo: common::BaseDemoInfo) -> impl leptos::IntoView {
view! {
<li>
<A href=format!("/demo/{}", demo.id)>Demo: {demo.id}</A>
</li>
}
}
#[leptos::component]
pub fn steam_login(height: &'static str, width: &'static str) -> impl leptos::IntoView {
view! {
<a href="/api/steam/login">
<img src="https://community.akamai.steamstatic.com/public/images/signinthroughsteam/sits_01.png" alt="Steam Login" style=format!("height: {height}; width: {width}") />
</a>
}
}
#[leptos::component]
pub fn upload_demo() -> impl leptos::IntoView {
use leptos_router::Form;
view! {
<div>
<Form action="/api/demos/upload" method="post" enctype="multipart/form-data".to_string()>
<p> Select File to upload </p>
<input type="file" name="demo" id="demo"></input>
<input type="submit" value="Upload Image" name="submit"></input>
</Form>
</div>
}
}
#[leptos::component]
pub fn top_bar() -> impl leptos::IntoView {
let style = stylers::style! {
"TopBar",
.bar {
width: 100%;
height: 4vh;
padding-top: 0.5vh;
padding-bottom: 0.5vh;
background-color: #28282f;
color: #d5d5d5;
}
.group {
display: inline-block;
}
.elem {
display: inline-block;
}
.logo {
color: #d5d5d5;
}
};
view! {class = style,
<div class="bar">
<A href="/" class="group">
<p class="logo">Knifer</p>
</A>
<div class="group" style="float: right">
<div class="elem">
Upload Demo
</div>
<div class="elem">
<SteamLogin height="4vh" width="auto" />
</div>
</div>
</div>
}
}
#[leptos::component]
pub fn homepage() -> impl leptos::IntoView {
let demo_data = create_resource(|| (), |_| async move {
let res = reqwasm::http::Request::get("/api/demos/list").send().await.unwrap();
let demos: Vec<common::BaseDemoInfo> = res.json().await.unwrap();
demos
});
view! {
<div>
<div>
<h2>Demos</h2>
<UploadDemo />
</div>
<ul>
{ move || demo_data.get().unwrap_or_default().into_iter().map(|demo| crate::DemoListEntry(DemoListEntryProps {
demo
})).collect::<Vec<_>>() }
</ul>
</div>
}
}

View File

@@ -1,11 +1,13 @@
use leptos::*; use leptos::*;
use leptos::prelude::*; use leptos_router::*;
async fn load_demos() -> usize { use frontend::{UploadDemo, TopBar, Homepage, Demo};
async fn load_demos() -> Vec<common::BaseDemoInfo> {
let res = reqwasm::http::Request::get("/api/demos/list").send().await.unwrap(); let res = reqwasm::http::Request::get("/api/demos/list").send().await.unwrap();
dbg!(res); let demos: Vec<common::BaseDemoInfo> = res.json().await.unwrap();
0 demos
} }
fn main() { fn main() {
@@ -14,16 +16,16 @@ fn main() {
}); });
mount_to_body(move || view! { mount_to_body(move || view! {
<p>"Hello, world!"</p> <Router>
<a href="/api/steam/login">Steam Login</a> { move || match async_data.get() { <nav>
None => 123, <TopBar />
Some(v) => v, </nav>
} } <main>
<Routes>
<form action="/api/demos/upload" method="post" enctype="multipart/form-data"> <Route path="/" view=Homepage />
Select File to upload <Route path="/demo/:id" view=Demo />
<input type="file" name="demo" id="demo"></input> </Routes>
<input type="submit" value="Upload Image" name="submit"></input> </main>
</form> </Router>
}) })
} }

View File

@@ -1,5 +1,6 @@
-- Your SQL goes here -- Your SQL goes here
CREATE TABLE IF NOT EXISTS demos ( CREATE TABLE IF NOT EXISTS demos (
steam_id bigint PRIMARY KEY, steam_id bigint,
demo_id bigint demo_id bigint,
PRIMARY KEY(steam_id, demo_id)
) )

View File

@@ -1,9 +1,9 @@
// @generated automatically by Diesel CLI. // @generated automatically by Diesel CLI.
diesel::table! { diesel::table! {
demos (steam_id) { demos (steam_id, demo_id) {
steam_id -> Int8, steam_id -> Int8,
demo_id -> Nullable<Int8>, demo_id -> Int8,
} }
} }