From 9e231babd6dcd1e6063d938247d0a12989523ebc Mon Sep 17 00:00:00 2001 From: transcaffeine Date: Sun, 11 May 2025 21:03:20 +0200 Subject: [PATCH] feat(api): build bingo card generation --- Cargo.lock | 87 ++++++++++++++++-------- Cargo.toml | 2 +- src/api/bingo_card.rs | 94 ++++++++++++++++++++++++++ src/api/mod.rs | 3 +- src/main.rs | 2 + src/model/bingo_card.rs | 8 +-- src/model/database.rs | 18 +++++ src/model/mod.rs | 4 +- src/sql/migrations/2024092501_init.sql | 4 +- 9 files changed, 183 insertions(+), 39 deletions(-) create mode 100644 src/api/bingo_card.rs diff --git a/Cargo.lock b/Cargo.lock index afc9b63..8eb98ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -439,7 +439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -632,7 +632,7 @@ dependencies = [ "hkdf", "pem-rfc7468", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", @@ -697,7 +697,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -756,9 +756,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -766,15 +766,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -794,27 +794,27 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", @@ -875,7 +875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1202,7 +1202,6 @@ dependencies = [ name = "ice-bingo" version = "0.1.0" dependencies = [ - "async-trait", "axum", "axum-auth", "axum-core", @@ -1210,6 +1209,7 @@ dependencies = [ "chrono", "config", "openidconnect", + "rand 0.9.1", "reqwest 0.12.15", "serde", "serde_json", @@ -1450,7 +1450,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -1501,7 +1501,7 @@ dependencies = [ "chrono", "getrandom 0.2.15", "http 0.2.12", - "rand", + "rand 0.8.5", "reqwest 0.11.27", "serde", "serde_json", @@ -1543,7 +1543,7 @@ dependencies = [ "oauth2", "p256", "p384", - "rand", + "rand 0.8.5", "rsa", "serde", "serde-value", @@ -1834,8 +1834,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1845,7 +1855,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1857,6 +1877,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.6" @@ -2001,7 +2030,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -2371,7 +2400,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -2541,7 +2570,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.5", "rsa", "serde", "sha1", @@ -2581,7 +2610,7 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2", diff --git a/Cargo.toml b/Cargo.toml index c57e472..ab31c95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,4 @@ reqwest = { version = "0.12.15", features = ["json"] } strum = { version = "0.27.1", features = ["derive"] } strum_macros = "0.27.1" chrono = { version = "0.4.41", features = ["alloc"] } -async-trait = "0.1.88" \ No newline at end of file +rand = "0.9.1" \ No newline at end of file diff --git a/src/api/bingo_card.rs b/src/api/bingo_card.rs new file mode 100644 index 0000000..f44f77a --- /dev/null +++ b/src/api/bingo_card.rs @@ -0,0 +1,94 @@ +use std::sync::Arc; +use rand::seq::SliceRandom; +use rand::rng; +use sqlx::PgPool; +use sqlx::postgres::PgQueryResult; +use uuid::Uuid; +use crate::model; +use crate::model::bingo_card::BingoCard; +use crate::model::database::BingoCardField; +use crate::model::database::Train; + +pub(crate) async fn get(id: Uuid, db: Arc) -> sqlx::Result { + let card = sqlx::query_as!( + crate::model::database::BingoCard, + "SELECT uuid as id FROM bingo_cards WHERE uuid = $1", + id, + ).fetch_one(db.as_ref()).await?; + let mut raw_fields = get_fields_with_train(card.id, db.clone()) + .await?; + raw_fields.sort_by(|a, b| (a.y * 5 + a.x).partial_cmp(&(b.y * 5 + b.x)).unwrap()); + raw_fields.truncate(24); + raw_fields.shrink_to_fit(); + let fields: [Train; 24] = raw_fields + .into_iter() + .map(|field| field.train) + .collect::>() + .try_into() + .unwrap_or_else(|v: Vec| panic!("Expected a Vec of length 24 but it was {}", v.len())); + Ok(BingoCard { + id: card.id, + fields + }) +} + +async fn get_fields_with_train(id: Uuid, db: Arc) -> sqlx::Result> { + sqlx::query_as( + "SELECT c.uuid, c.x_pos, c.y_pos, c.card_uuid, t.uuid, t.tz_id, t.name FROM bingo_card c LEFT JOIN triebzug t ON t.uuid = c.triebzug WHERE card_uuid = $1" + ) + .bind(id) + .fetch_all(db.as_ref()) + .await +} + +pub(crate) async fn generate(db: Arc) -> sqlx::Result { + let card_id = Uuid::new_v4(); + let mut all_trains = fetch_all(db.clone()).await?; + all_trains.shuffle(&mut rng()); + all_trains.truncate(24); + all_trains.shrink_to_fit(); + let trains = all_trains.to_owned(); + let fields = trains + .into_iter() + .enumerate() + .map(|(idx, train)| BingoCardField { + uuid: Uuid::new_v4(), + x: (idx as i32).rem_euclid(5), + y: (idx as i32).div_euclid(5), + train, + card_id + }) + .collect::>(); + let card = model::database::BingoCard { + id: card_id, + }; + save_card(card, db.clone()).await?; + for field in fields { + save_card_field(field, db.clone()).await?; + } + get(card_id, db.clone()).await +} + +async fn save_card(card: model::database::BingoCard, db: Arc) -> sqlx::Result { + sqlx::query!( + "INSERT INTO bingo_cards VALUES ($1, NOW(), NOW() + interval '7 day')", + card.id + ).execute(db.as_ref()).await +} + +async fn save_card_field(field: BingoCardField, db: Arc) -> sqlx::Result { + sqlx::query!( + "INSERT INTO bingo_card VALUES ($1, $2, $3, $4, $5)", + field.uuid, + field.x, + field.y, + field.train.uuid, + field.card_id + ).execute(db.as_ref()).await +} + +async fn fetch_all(db: Arc) -> sqlx::Result> { + sqlx::query_as("SELECT * FROM triebzug") + .fetch_all(db.as_ref()) + .await +} \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs index a289425..1a57631 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod handlers; -mod db_vendo_navigator; \ No newline at end of file +pub(crate) mod bingo_card; +mod db_vendo_navigator; diff --git a/src/main.rs b/src/main.rs index e36f6a4..2940469 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,8 @@ async fn main() { let state: AppState = AppState::new(config.clone()).await.unwrap(); migrate(state.clone()).await.expect("Database migration failed"); + crate::api::bingo_card::generate(state.db.clone()).await.unwrap(); + let router = build_router(state); let listener = listen(config).await; axum::serve::serve(listener, router).await.unwrap() diff --git a/src/model/bingo_card.rs b/src/model/bingo_card.rs index 85343d8..264db1a 100644 --- a/src/model/bingo_card.rs +++ b/src/model/bingo_card.rs @@ -1,6 +1,6 @@ -use crate::model::train::Train; +use crate::model::database::Train; -struct BingoCard { - id: uuid::Uuid, - fields: [Train; 24], +pub struct BingoCard { + pub id: uuid::Uuid, + pub fields: [Train; 24], } \ No newline at end of file diff --git a/src/model/database.rs b/src/model/database.rs index bc5a649..b6dcd1f 100644 --- a/src/model/database.rs +++ b/src/model/database.rs @@ -11,4 +11,22 @@ pub struct Train { pub uuid: Uuid, pub tz_id: i32, pub name: Option, +} + +#[derive(sqlx::FromRow, Debug, Clone)] +pub struct BingoCardField { + pub uuid: Uuid, + #[sqlx(rename = "x_pos")] + pub x: i32, + #[sqlx(rename = "y_pos")] + pub y: i32, + #[sqlx(rename = "card_uuid")] + pub card_id: Uuid, + #[sqlx(flatten, rename = "triebzug")] + pub train: Train, +} + +#[derive(sqlx::FromRow, Debug, Clone)] +pub struct BingoCard { + pub id: Uuid, } \ No newline at end of file diff --git a/src/model/mod.rs b/src/model/mod.rs index 2e2912f..bf21a70 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -2,7 +2,7 @@ pub mod uic; pub mod db_vendo_navigator_api; pub(crate) mod travelynx; pub(crate) mod traewelling; -mod bingo_card; -mod train; +pub(crate) mod bingo_card; +pub(crate) mod train; pub(crate) mod app; pub(crate) mod database; \ No newline at end of file diff --git a/src/sql/migrations/2024092501_init.sql b/src/sql/migrations/2024092501_init.sql index 48da215..8f3f26f 100644 --- a/src/sql/migrations/2024092501_init.sql +++ b/src/sql/migrations/2024092501_init.sql @@ -7,8 +7,8 @@ CREATE TABLE IF NOT EXISTS triebzug ( CREATE TABLE IF NOT EXISTS bingo_cards ( uuid UUID PRIMARY KEY, - start_time INT NOT NULL, - end_time INT NOT NULL + start_time TIMESTAMP NOT NULL, + end_time TIMESTAMP NOT NULL ); CREATE TABLE IF NOT EXISTS bingo_card (