From ae6c1b590f00848ff31bc7862985d94d8b6ac97e Mon Sep 17 00:00:00 2001 From: Lol3rrr Date: Sat, 7 Sep 2024 17:44:57 +0200 Subject: [PATCH] Start DB integration --- Cargo.lock | 363 ++++++++++++++++++ README.md | 3 + backend/Cargo.toml | 7 +- backend/src/diesel_sessionstore.rs | 98 +++++ backend/src/lib.rs | 58 +-- backend/src/main.rs | 27 +- backend/src/models.rs | 18 + backend/src/schema.rs | 14 + backend/src/usersession.rs | 58 +++ diesel.toml | 9 + migrations/.keep | 0 .../down.sql | 6 + .../up.sql | 36 ++ .../2024-09-07-130446_sessions/down.sql | 2 + migrations/2024-09-07-130446_sessions/up.sql | 6 + migrations/2024-09-07-151517_demos/down.sql | 2 + migrations/2024-09-07-151517_demos/up.sql | 5 + src/schema.rs | 21 + 18 files changed, 680 insertions(+), 53 deletions(-) create mode 100644 backend/src/diesel_sessionstore.rs create mode 100644 backend/src/models.rs create mode 100644 backend/src/schema.rs create mode 100644 backend/src/usersession.rs create mode 100644 diesel.toml create mode 100644 migrations/.keep create mode 100644 migrations/00000000000000_diesel_initial_setup/down.sql create mode 100644 migrations/00000000000000_diesel_initial_setup/up.sql create mode 100644 migrations/2024-09-07-130446_sessions/down.sql create mode 100644 migrations/2024-09-07-130446_sessions/up.sql create mode 100644 migrations/2024-09-07-151517_demos/down.sql create mode 100644 migrations/2024-09-07-151517_demos/up.sql create mode 100644 src/schema.rs diff --git a/Cargo.lock b/Cargo.lock index 764ebda..dbaeabd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,8 +152,12 @@ version = "0.1.0" dependencies = [ "async-trait", "axum", + "diesel", + "diesel-async", + "diesel_async_migrations", "futures-util", "serde", + "serde_json", "steam-openid", "time", "tokio", @@ -202,6 +206,15 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -344,12 +357,66 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + [[package]] name = "crunchy" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -384,12 +451,109 @@ dependencies = [ "syn", ] +[[package]] +name = "diesel" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158fe8e2e68695bd615d7e4f3227c0727b151330d3e253b525086c348d055d5e" +dependencies = [ + "bitflags 2.6.0", + "byteorder", + "diesel_derives", + "itoa", + "serde_json", +] + +[[package]] +name = "diesel-async" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb799bb6f8ca6a794462125d7b8983b0c86e6c93a33a9c55934a4a5de4409d3" +dependencies = [ + "async-trait", + "diesel", + "futures-util", + "scoped-futures", + "tokio", + "tokio-postgres", +] + +[[package]] +name = "diesel_async_migrations" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99915cbb9455e8fd56f12edc58f92bbdf8161e4363cb2000cf4308aa6358ff4" +dependencies = [ + "diesel", + "diesel-async", + "diesel_async_migrations_macros", + "scoped-futures", + "tracing", +] + +[[package]] +name = "diesel_async_migrations_macros" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05de210f31e6ac18162501b03c37f839af9f9fd6dd6de2bb4031ae6691c47679" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "diesel_derives" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" +dependencies = [ + "diesel_table_macro_syntax", + "dsl_auto_type", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +dependencies = [ + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "drain_filter_polyfill" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" +[[package]] +name = "dsl_auto_type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" +dependencies = [ + "darling", + "either", + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.13.0" @@ -421,6 +585,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastrand" version = "2.1.1" @@ -554,6 +724,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -675,12 +855,27 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html-escape" version = "0.2.13" @@ -835,6 +1030,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1108,6 +1309,16 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1339,6 +1550,24 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.5" @@ -1377,6 +1606,35 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "postgres-protocol" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acda0ebdebc28befa84bee35e651e4c5f09073d668c7aed4cf7e23c3cda84b23" +dependencies = [ + "base64 0.22.1", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02048d9e032fb3cc3413bbf7b83a15d84a5d419778e2628751896d856498eee9" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1695,6 +1953,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "scoped-futures" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1473e24c637950c9bd38763220bea91ec3e095a89f672bbd7a10d03e77ba467" +dependencies = [ + "cfg-if", + "pin-utils", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1877,6 +2145,17 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1892,6 +2171,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -1947,6 +2232,29 @@ dependencies = [ "url", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.77" @@ -2129,6 +2437,32 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-postgres" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03adcf0147e203b6032c0b2d30be1415ba03bc348901f3ff1cc0df6a733e60c3" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand", + "socket2", + "tokio", + "tokio-util", + "whoami", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -2381,6 +2715,12 @@ dependencies = [ "syn", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicase" version = "2.7.0" @@ -2411,6 +2751,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" + [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -2492,6 +2838,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.93" @@ -2584,6 +2936,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "whoami" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" +dependencies = [ + "redox_syscall", + "wasite", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/README.md b/README.md index fb7d5b3..156ed3f 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,6 @@ A self-hosted demo analysis tool ### Backend 1. Navigate to the root folder 2. Run `cargo run --bin backend` + +### DB Stuff +We use [diesel]() as the ORM and using the cli for all the migrations diff --git a/backend/Cargo.toml b/backend/Cargo.toml index dc7b832..21b1a5d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -8,10 +8,15 @@ async-trait = "0.1.82" axum = { version = "0.7.5", features = ["multipart"] } serde = { version = "1.0.210", features = ["derive"] } steam-openid = "0.2.0" -time = "0.3.36" +time = { version = "0.3.36", features = ["formatting", "parsing"] } tokio = { version = "1.40.0", features = ["rt", "macros", "net", "mio"] } tower-sessions = "0.13.0" tower-http = { version = "0.5", features = ["fs"] } tracing = { version = "0.1.40", features = ["async-await"] } tracing-subscriber = "0.3.18" futures-util = "0.3" + +diesel = { version = "2.2", features = ["serde_json"] } +diesel-async = { version = "0.5", features = ["postgres"] } +serde_json = "1.0.128" +diesel_async_migrations = { version = "0.15" } diff --git a/backend/src/diesel_sessionstore.rs b/backend/src/diesel_sessionstore.rs new file mode 100644 index 0000000..1670c82 --- /dev/null +++ b/backend/src/diesel_sessionstore.rs @@ -0,0 +1,98 @@ +use diesel::prelude::*; +use diesel_async::RunQueryDsl; + +#[derive(Debug, Clone)] +pub struct DieselStore {} + +static EXPIRY_FORMAT: std::sync::LazyLock<&[time::format_description::BorrowedFormatItem<'static>]> = std::sync::LazyLock::new(|| { + time::macros::format_description!( + "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory]:[offset_minute]:[offset_second]" + ) + }); + +impl DieselStore { + + pub fn new() -> Self { + Self {} + } + + fn id_to_bytes(&self, val: i128) -> Vec { + let id_bytes = val.to_be_bytes(); + vec![i64::from_be_bytes((id_bytes[0..8]).try_into().unwrap()), i64::from_be_bytes((id_bytes[8..16]).try_into().unwrap())] + } + fn bytes_to_id(&self, val: Vec) -> i128 { + assert_eq!(2, val.len()); + + let fb = val[0].to_be_bytes(); + let sb = val[1].to_be_bytes(); + + i128::from_be_bytes([fb[0], fb[1], fb[2], fb[3], fb[4], fb[5], fb[6], fb[7], sb[0], sb[1], sb[2], sb[3], sb[4], sb[5], sb[6], sb[7]]) + } + + fn expiry_to_string(&self, expiry_date: &time::OffsetDateTime) -> String { + expiry_date.format(&EXPIRY_FORMAT).unwrap() + } + fn string_to_expiry(&self, input: &str) -> time::OffsetDateTime { + time::OffsetDateTime::parse(input, &EXPIRY_FORMAT).unwrap() + } +} + +#[async_trait::async_trait] +impl tower_sessions::SessionStore for DieselStore { + async fn save(&self,session_record: &tower_sessions::session::Record) -> tower_sessions::session_store::Result<()> { + let db_id = self.id_to_bytes(session_record.id.0); + + let data = serde_json::to_value(&session_record.data).unwrap(); + let expiry_date = self.expiry_to_string(&session_record.expiry_date); + + let query = diesel::dsl::insert_into(crate::schema::sessions::dsl::sessions) + .values(crate::models::Session { + id: db_id, + data: data.clone(), + expiry_date: expiry_date.clone(), + }) + .on_conflict(crate::schema::sessions::dsl::id) + .do_update() + .set((crate::schema::sessions::dsl::data.eq(data), crate::schema::sessions::dsl::expiry_date.eq(expiry_date))); + + let mut connection = crate::db_connection().await; + + query.execute(&mut connection).await.unwrap(); + + Ok(()) + } + + async fn load(&self,session_id: &tower_sessions::session::Id) -> tower_sessions::session_store::Result> { + let db_id = self.id_to_bytes(session_id.0); + + let query = crate::schema::sessions::dsl::sessions.filter(crate::schema::sessions::dsl::id.eq(db_id)); + + let mut connection = crate::db_connection().await; + + let mut result: Vec = query.load(&mut connection).await.unwrap(); + + if result.len() > 1 { + tracing::error!("Found more than 1 result"); + return Err(tower_sessions::session_store::Error::Backend("Found more than 1 result".to_string())); + } + + let result = result.pop().unwrap(); + + Ok(Some(tower_sessions::session::Record { + id: tower_sessions::session::Id(self.bytes_to_id(result.id)), + data: serde_json::from_value(result.data).unwrap(), + expiry_date: self.string_to_expiry(&result.expiry_date), + })) + } + + async fn delete(&self,session_id: &tower_sessions::session::Id) -> tower_sessions::session_store::Result<()> { + let db_id = self.id_to_bytes(session_id.0); + + let query = crate::schema::sessions::dsl::sessions.filter(crate::schema::sessions::dsl::id.eq(db_id)); + + let mut connection = crate::db_connection().await; + diesel::dsl::delete(query).execute(&mut connection).await.unwrap(); + + Ok(()) + } +} diff --git a/backend/src/lib.rs b/backend/src/lib.rs index ce0e305..6f012c5 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,59 +1,19 @@ use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct UserSessionData { - pub steam_id: Option, -} +pub mod models; +pub mod schema; -impl Default for UserSessionData { - fn default() -> Self { - Self { steam_id: None } - } -} +mod usersession; +pub use usersession::{UserSessionData, UserSession}; -pub struct UserSession { - pub session: tower_sessions::Session, - data: UserSessionData, -} +pub mod diesel_sessionstore; -impl UserSession { - const KEY: &'static str = "user.data"; +pub async fn db_connection() -> diesel_async::AsyncPgConnection { + use diesel_async::AsyncConnection; - pub fn data(&self) -> &UserSessionData { - &self.data - } + let database_url = std::env::var("DATABASE_URL").expect("'DATABASE_URL' must be set"); - pub async fn modify_data(&mut self, func: F) - where - F: FnOnce(&mut UserSessionData), - { - let mut entry = &mut self.data; - func(&mut entry); - - self.session.insert(Self::KEY, entry).await.unwrap(); - } -} - -#[async_trait::async_trait] -impl axum::extract::FromRequestParts for UserSession -where - S: Send + Sync, -{ - type Rejection = (axum::http::StatusCode, &'static str); - - async fn from_request_parts( - req: &mut axum::http::request::Parts, - state: &S, - ) -> Result { - let session = tower_sessions::Session::from_request_parts(req, state).await?; - - let guest_data: UserSessionData = session.get(Self::KEY).await.unwrap().unwrap_or_default(); - - Ok(Self { - session, - data: guest_data, - }) - } + diesel_async::AsyncPgConnection::establish(&database_url).await.unwrap_or_else(|e| panic!("Error connecting to {} - {:?}", database_url, e)) } pub async fn get_demo_from_upload(name: &str, mut form: axum::extract::Multipart) -> Option { diff --git a/backend/src/main.rs b/backend/src/main.rs index 1a19254..90302e2 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,10 +1,19 @@ use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt; +use diesel::prelude::*; +use diesel_async::{RunQueryDsl, AsyncConnection, AsyncPgConnection}; + static OPENID: std::sync::LazyLock = std::sync::LazyLock::new(|| { steam_openid::SteamOpenId::new("http://192.168.0.156:3000", "/api/steam/callback").unwrap() }); static UPLOAD_FOLDER: &str = "uploads/"; +const MIGRATIONS: diesel_async_migrations::EmbeddedMigrations = diesel_async_migrations::embed_migrations!("../migrations/"); + +async fn run_migrations(connection: &mut diesel_async::AsyncPgConnection) { + MIGRATIONS.run_pending_migrations(connection).await.unwrap(); +} + #[tokio::main(flavor = "current_thread")] async fn main() { let registry = tracing_subscriber::Registry::default() @@ -16,7 +25,11 @@ async fn main() { tracing::info!("Starting..."); - let session_store = tower_sessions::MemoryStore::default(); + tracing::info!("Applying Migrations"); + run_migrations(&mut backend::db_connection().await).await; + tracing::info!("Completed Migrations"); + + let session_store = backend::diesel_sessionstore::DieselStore::new(); let session_layer = tower_sessions::SessionManagerLayer::new(session_store) .with_secure(false) .with_expiry(tower_sessions::Expiry::OnInactivity( @@ -60,8 +73,11 @@ async fn upload(session: backend::UserSession, form: axum::extract::Multipart) - tokio::fs::write(demo_file_path, file_content).await.unwrap(); - // TODO - // Insert Demo into users list of demos and possibly queue demo for analysis? + 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("/")) } @@ -101,5 +117,10 @@ async fn demos_list(session: backend::UserSession) -> Result<(), axum::http::Sta 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 = query.load(&mut backend::db_connection().await).await.unwrap(); + + dbg!(&results); + Ok(()) } diff --git a/backend/src/models.rs b/backend/src/models.rs new file mode 100644 index 0000000..c7235c0 --- /dev/null +++ b/backend/src/models.rs @@ -0,0 +1,18 @@ +use diesel::prelude::*; + +#[derive(Queryable, Selectable, Insertable, Debug)] +#[diesel(table_name = crate::schema::sessions)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Session { + pub id: Vec, + pub data: serde_json::Value, + pub expiry_date: String, +} + +#[derive(Queryable, Selectable, Insertable, Debug)] +#[diesel(table_name = crate::schema::demos)] +#[diesel(check_for_backend(diesel::pg::Pg))] +pub struct Demo { + pub steam_id: i64, + pub demo_id: i64, +} diff --git a/backend/src/schema.rs b/backend/src/schema.rs new file mode 100644 index 0000000..3503f58 --- /dev/null +++ b/backend/src/schema.rs @@ -0,0 +1,14 @@ +diesel::table! { + sessions (id) { + id -> Array, + data -> Jsonb, + expiry_date -> Text, + } +} + +diesel::table! { + demos (steam_id) { + steam_id -> BigInt, + demo_id -> BigInt + } +} diff --git a/backend/src/usersession.rs b/backend/src/usersession.rs new file mode 100644 index 0000000..cf72207 --- /dev/null +++ b/backend/src/usersession.rs @@ -0,0 +1,58 @@ +use diesel::prelude::*; +use diesel_async::RunQueryDsl; + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct UserSessionData { + pub steam_id: Option, +} + +impl Default for UserSessionData { + fn default() -> Self { + Self { steam_id: None } + } +} + +pub struct UserSession { + pub session: tower_sessions::Session, + data: UserSessionData, +} + +impl UserSession { + const KEY: &'static str = "user.data"; + + pub fn data(&self) -> &UserSessionData { + &self.data + } + + pub async fn modify_data(&mut self, func: F) + where + F: FnOnce(&mut UserSessionData), + { + let mut entry = &mut self.data; + func(&mut entry); + + self.session.insert(Self::KEY, entry).await.unwrap(); + } +} + +#[async_trait::async_trait] +impl axum::extract::FromRequestParts for UserSession +where + S: Send + Sync, +{ + type Rejection = (axum::http::StatusCode, &'static str); + + async fn from_request_parts( + req: &mut axum::http::request::Parts, + state: &S, + ) -> Result { + let session = tower_sessions::Session::from_request_parts(req, state).await?; + + let guest_data: UserSessionData = session.get(Self::KEY).await.unwrap().unwrap_or_default(); + + Ok(Self { + session, + data: guest_data, + }) + } +} diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000..a56cd53 --- /dev/null +++ b/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "/Users/leon/Documents/coding/rust/knifer/migrations" diff --git a/migrations/.keep b/migrations/.keep new file mode 100644 index 0000000..e69de29 diff --git a/migrations/00000000000000_diesel_initial_setup/down.sql b/migrations/00000000000000_diesel_initial_setup/down.sql new file mode 100644 index 0000000..a9f5260 --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/migrations/00000000000000_diesel_initial_setup/up.sql b/migrations/00000000000000_diesel_initial_setup/up.sql new file mode 100644 index 0000000..d68895b --- /dev/null +++ b/migrations/00000000000000_diesel_initial_setup/up.sql @@ -0,0 +1,36 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + + + + +-- Sets up a trigger for the given table to automatically set a column called +-- `updated_at` whenever the row is modified (unless `updated_at` was included +-- in the modified columns) +-- +-- # Example +-- +-- ```sql +-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); +-- +-- SELECT diesel_manage_updated_at('users'); +-- ``` +CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ +BEGIN + EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s + FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ +BEGIN + IF ( + NEW IS DISTINCT FROM OLD AND + NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at + ) THEN + NEW.updated_at := current_timestamp; + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; diff --git a/migrations/2024-09-07-130446_sessions/down.sql b/migrations/2024-09-07-130446_sessions/down.sql new file mode 100644 index 0000000..11998cd --- /dev/null +++ b/migrations/2024-09-07-130446_sessions/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE sessions diff --git a/migrations/2024-09-07-130446_sessions/up.sql b/migrations/2024-09-07-130446_sessions/up.sql new file mode 100644 index 0000000..5444501 --- /dev/null +++ b/migrations/2024-09-07-130446_sessions/up.sql @@ -0,0 +1,6 @@ +-- Your SQL goes here +CREATE TABLE IF NOT EXISTS sessions ( + id bigint[2] PRIMARY KEY, + data jsonb, + expiry_date TEXT +) diff --git a/migrations/2024-09-07-151517_demos/down.sql b/migrations/2024-09-07-151517_demos/down.sql new file mode 100644 index 0000000..f200b8a --- /dev/null +++ b/migrations/2024-09-07-151517_demos/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE demos diff --git a/migrations/2024-09-07-151517_demos/up.sql b/migrations/2024-09-07-151517_demos/up.sql new file mode 100644 index 0000000..46fba7f --- /dev/null +++ b/migrations/2024-09-07-151517_demos/up.sql @@ -0,0 +1,5 @@ +-- Your SQL goes here +CREATE TABLE IF NOT EXISTS demos ( + steam_id bigint PRIMARY KEY, + demo_id bigint +) diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000..5244f6d --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,21 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + demos (steam_id) { + steam_id -> Int8, + demo_id -> Nullable, + } +} + +diesel::table! { + sessions (id) { + id -> Array>, + data -> Nullable, + expiry_date -> Nullable, + } +} + +diesel::allow_tables_to_appear_in_same_query!( + demos, + sessions, +);