From 5c27be01911fecb761e772a45d232ad4d0fc95f9 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sat, 28 May 2022 18:25:53 +0200 Subject: [PATCH 1/7] Schema rewrite to split up the schema.rs file. --- .../down.sql | 3 + .../up.sql | 9 + backend/src/api.rs | 72 ++-- backend/src/schema.rs | 360 ------------------ backend/src/schema/gamenight.rs | 161 ++++++++ backend/src/schema/mod.rs | 60 +++ backend/src/schema/users.rs | 191 ++++++++++ 7 files changed, 456 insertions(+), 400 deletions(-) create mode 100644 backend/migrations/2022-05-28-142526_gamenight participants/down.sql create mode 100644 backend/migrations/2022-05-28-142526_gamenight participants/up.sql delete mode 100644 backend/src/schema.rs create mode 100644 backend/src/schema/gamenight.rs create mode 100644 backend/src/schema/mod.rs create mode 100644 backend/src/schema/users.rs diff --git a/backend/migrations/2022-05-28-142526_gamenight participants/down.sql b/backend/migrations/2022-05-28-142526_gamenight participants/down.sql new file mode 100644 index 0000000..e32c462 --- /dev/null +++ b/backend/migrations/2022-05-28-142526_gamenight participants/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +drop table gamenight_participants; \ No newline at end of file diff --git a/backend/migrations/2022-05-28-142526_gamenight participants/up.sql b/backend/migrations/2022-05-28-142526_gamenight participants/up.sql new file mode 100644 index 0000000..1115fc9 --- /dev/null +++ b/backend/migrations/2022-05-28-142526_gamenight participants/up.sql @@ -0,0 +1,9 @@ +-- Your SQL goes here + +create table gamenight_participants ( + gamenight_id UUID NOT NULL, + user_id UUID NOT NULL, + CONSTRAINT FK_gamenight_id FOREIGN KEY (gamenight_id) REFERENCES gamenight(id) ON DELETE CASCADE, + CONSTRAINT FK_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + PRIMARY KEY(gamenight_id, user_id) +) \ No newline at end of file diff --git a/backend/src/api.rs b/backend/src/api.rs index 5f7a959..17608fd 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -1,7 +1,8 @@ -use uuid::Uuid; -use crate::schema; +use crate::schema::users::*; +use crate::schema::gamenight::*; use crate::schema::DbConn; use crate::AppConfig; +use uuid::Uuid; use chrono::Utc; use jsonwebtoken::decode; use jsonwebtoken::encode; @@ -20,16 +21,7 @@ use validator::ValidateArgs; #[derive(Debug, Responder)] pub enum ApiResponseVariant { Status(Status), - // Redirect(Redirect), Value(Value), - // Flash(Flash) -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct UserWithToken { - #[serde(flatten)] - pub user: schema::User, - pub jwt: String, } #[derive(Serialize, Deserialize, Debug)] @@ -40,9 +32,9 @@ struct ApiResponse { #[serde(skip_serializing_if = "Option::is_none")] user: Option, #[serde(skip_serializing_if = "Option::is_none")] - gamenights: Option>, + gamenights: Option>, #[serde(skip_serializing_if = "Option::is_none")] - games: Option>, + games: Option>, } impl ApiResponse { @@ -67,7 +59,7 @@ impl ApiResponse { } } - fn login_response(user: schema::User, jwt: String) -> Self { + fn login_response(user: User, jwt: String) -> Self { Self { result: Self::SUCCES_RESULT, message: None, @@ -80,7 +72,7 @@ impl ApiResponse { } } - fn gamenight_response(gamenights: Vec) -> Self { + fn gamenight_response(gamenights: Vec) -> Self { Self { result: Self::SUCCES_RESULT, message: None, @@ -90,7 +82,7 @@ impl ApiResponse { } } - fn games_response(games: Vec) -> Self { + fn games_response(games: Vec) -> Self { Self { result: Self::SUCCES_RESULT, message: None, @@ -110,7 +102,7 @@ const AUTH_HEADER: &str = "Authorization"; const BEARER: &str = "Bearer "; #[rocket::async_trait] -impl<'r> FromRequest<'r> for schema::User { +impl<'r> FromRequest<'r> for User { type Error = ApiError; async fn from_request(req: &'r Request<'_>) -> Outcome { @@ -140,7 +132,7 @@ impl<'r> FromRequest<'r> for schema::User { let id = token.claims.uid; let conn = req.guard::().await.unwrap(); - return match schema::get_user(conn, id).await { + return match get_user(conn, id).await { Ok(o) => Outcome::Success(o), Err(_) => Outcome::Forward(()) } @@ -148,8 +140,8 @@ impl<'r> FromRequest<'r> for schema::User { } #[get("/gamenights")] -pub async fn gamenights(conn: DbConn, _user: schema::User) -> ApiResponseVariant { - match schema::get_all_gamenights(conn).await { +pub async fn gamenights(conn: DbConn, _user: User) -> ApiResponseVariant { + match get_all_gamenights(conn).await { Ok(gamenights) => ApiResponseVariant::Value(json!(ApiResponse::gamenight_response(gamenights))), Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) } @@ -165,13 +157,13 @@ pub struct GameNightInput { pub name: String, pub datetime: String, pub owner_id: Option, - pub game_list: Vec, + pub game_list: Vec, } -impl Into for GameNightInput { +impl Into for GameNightInput { - fn into(self) -> schema::GameNight { - schema::GameNight { + fn into(self) -> GameNight { + GameNight { id: Uuid::new_v4(), name: self.name, datetime: self.datetime, @@ -183,7 +175,7 @@ impl Into for GameNightInput { #[post("/gamenights", format = "application/json", data = "")] pub async fn gamenights_post_json( conn: DbConn, - user: schema::User, + user: User, gamenight_json: Json, ) -> ApiResponseVariant { let mut gamenight = gamenight_json.into_inner(); @@ -191,12 +183,12 @@ pub async fn gamenights_post_json( let mut mutable_game_list = gamenight.game_list.clone(); - match schema::add_unknown_games(&conn, &mut mutable_game_list).await { + match add_unknown_games(&conn, &mut mutable_game_list).await { Ok(_) => (), Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) }; - match schema::insert_gamenight(conn, gamenight.clone().into(), mutable_game_list).await { + match insert_gamenight(conn, gamenight.clone().into(), mutable_game_list).await { Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) } @@ -210,20 +202,20 @@ pub async fn gamenights_post_json_unauthorized() -> ApiResponseVariant { #[delete("/gamenights", format = "application/json", data = "")] pub async fn gamenights_delete_json( conn: DbConn, - user: schema::User, - delete_gamenight_json: Json + user: User, + delete_gamenight_json: Json ) -> ApiResponseVariant { - if user.role == schema::Role::Admin { - if let Err(error) = schema::delete_gamenight(&conn, delete_gamenight_json.game_id).await { + if user.role == Role::Admin { + if let Err(error) = delete_gamenight(&conn, delete_gamenight_json.game_id).await { return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) } return ApiResponseVariant::Value(json!(ApiResponse::SUCCES)) } - match schema::get_gamenight(&conn, delete_gamenight_json.game_id).await { + match get_gamenight(&conn, delete_gamenight_json.game_id).await { Ok(gamenight) => { if user.id == gamenight.owner_id { - if let Err(error) = schema::delete_gamenight(&conn, delete_gamenight_json.game_id).await { + if let Err(error) = delete_gamenight(&conn, delete_gamenight_json.game_id).await { return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) } return ApiResponseVariant::Value(json!(ApiResponse::SUCCES)) @@ -243,7 +235,7 @@ pub async fn gamenights_delete_json_unauthorized() -> ApiResponseVariant { #[post("/register", format = "application/json", data = "")] pub async fn register_post_json( conn: DbConn, - register_json: Json, + register_json: Json, ) -> ApiResponseVariant { let register = register_json.into_inner(); let register_clone = register.clone(); @@ -257,7 +249,7 @@ pub async fn register_post_json( } } - match schema::insert_user(conn, register).await { + match insert_user(conn, register).await { Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), } @@ -267,16 +259,16 @@ pub async fn register_post_json( struct Claims { exp: i64, uid: Uuid, - role: schema::Role, + role: Role, } #[post("/login", format = "application/json", data = "")] pub async fn login_post_json( conn: DbConn, config: &State, - login_json: Json, + login_json: Json, ) -> ApiResponseVariant { - match schema::login(conn, login_json.into_inner()).await { + match login(conn, login_json.into_inner()).await { Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), Ok(login_result) => { if !login_result.result { @@ -308,8 +300,8 @@ pub async fn login_post_json( } #[get("/games")] -pub async fn games(conn: DbConn, _user: schema::User) -> ApiResponseVariant { - match schema::get_all_known_games(&conn).await { +pub async fn games(conn: DbConn, _user: User) -> ApiResponseVariant { + match get_all_known_games(&conn).await { Ok(games) => ApiResponseVariant::Value(json!(ApiResponse::games_response(games))), Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) } diff --git a/backend/src/schema.rs b/backend/src/schema.rs deleted file mode 100644 index 2da917d..0000000 --- a/backend/src/schema.rs +++ /dev/null @@ -1,360 +0,0 @@ -use uuid::Uuid; -use crate::diesel::Connection; -use crate::diesel::ExpressionMethods; -use crate::diesel::QueryDsl; -use argon2::password_hash::SaltString; -use argon2::PasswordHash; -use argon2::PasswordVerifier; -use argon2::{ - password_hash::{rand_core::OsRng, PasswordHasher}, - Argon2, -}; -use diesel::RunQueryDsl; -use diesel_derive_enum::DbEnum; -use rocket::{Build, Rocket}; -use rocket_sync_db_pools::database; -use serde::{Deserialize, Serialize}; -use std::ops::Deref; -use validator::{Validate, ValidationError}; - -#[database("gamenight_database")] -pub struct DbConn(diesel::PgConnection); - -impl Deref for DbConn { - type Target = rocket_sync_db_pools::Connection; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -table! { - gamenight (id) { - id -> diesel::sql_types::Uuid, - name -> VarChar, - datetime -> VarChar, - owner_id -> Uuid, - } -} - -table! { - known_games (id) { - id -> diesel::sql_types::Uuid, - name -> VarChar, - } -} - -table! { - users(id) { - id -> diesel::sql_types::Uuid, - username -> VarChar, - email -> VarChar, - role -> crate::schema::RoleMapping, - } -} - -table! { - pwd(user_id) { - user_id -> diesel::sql_types::Uuid, - password -> VarChar, - } -} - -table! { - gamenight_gamelist(gamenight_id, game_id) { - gamenight_id -> diesel::sql_types::Uuid, - game_id -> diesel::sql_types::Uuid, - } -} - -allow_tables_to_appear_in_same_query!(gamenight, known_games,); - -pub enum DatabaseError { - Hash(password_hash::Error), - Query(String), -} - -impl From for DatabaseError { - fn from(error: diesel::result::Error) -> Self { - Self::Query(error.to_string()) - } -} - -impl From for DatabaseError { - fn from(error: password_hash::Error) -> Self { - Self::Hash(error) - } -} - -impl std::fmt::Display for DatabaseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - match self { - DatabaseError::Hash(err) => write!(f, "{}", err), - DatabaseError::Query(err) => write!(f, "{}", err), - } - } -} - -pub async fn get_all_gamenights(conn: DbConn) -> Result, DatabaseError> { - Ok(conn.run(|c| - gamenight::table.load::(c) - ).await?) -} - -pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNight, game_list: Vec) -> Result { - Ok(conn.run(move |c| - c.transaction(|| { - diesel::insert_into(gamenight::table) - .values(&new_gamenight) - .execute(c)?; - - let entries: Vec = game_list.iter().map( - |g| GamenightGameListEntry { gamenight_id: new_gamenight.id.clone(), game_id: g.id.clone() } - ).collect(); - - diesel::insert_into(gamenight_gamelist::table) - .values(entries) - .execute(c) - }) - ).await?) -} - -pub async fn get_gamenight(conn: &DbConn, game_id: Uuid) -> Result { - Ok(conn.run(move |c| - gamenight::table.find(game_id).first(c) - ).await?) -} - -pub async fn delete_gamenight(conn: &DbConn, game_id: Uuid) -> Result { - Ok(conn.run(move |c| - diesel::delete( - gamenight::table.filter( - gamenight::id.eq(game_id) - ) - ).execute(c) - ).await?) -} - -pub async fn insert_user(conn: DbConn, new_user: Register) -> Result { - let salt = SaltString::generate(&mut OsRng); - - let argon2 = Argon2::default(); - - let password_hash = argon2.hash_password(new_user.password.as_bytes(), &salt)?.to_string(); - - Ok(conn.run(move |c| { - c.transaction(|| { - let id = Uuid::new_v4(); - - diesel::insert_into(users::table) - .values(User { - id: id.clone(), - username: new_user.username, - email: new_user.email, - role: Role::User - }) - .execute(c)?; - - diesel::insert_into(pwd::table) - .values(Pwd { - user_id: id, - password: password_hash - }) - .execute(c) - }) - }) - .await?) -} - -pub async fn login(conn: DbConn, login: Login) -> Result { - conn.run(move |c| -> Result { - let id: Uuid = users::table - .filter(users::username.eq(&login.username)) - .or_filter(users::email.eq(&login.username)) - .select(users::id) - .first(c)?; - - let pwd: String = pwd::table - .filter(pwd::user_id.eq(id)) - .select(pwd::password) - .first(c)?; - - let parsed_hash = PasswordHash::new(&pwd)?; - - if Argon2::default() - .verify_password(&login.password.as_bytes(), &parsed_hash) - .is_ok() - { - let user: User = users::table.find(id).first(c)?; - - Ok(LoginResult { - result: true, - user : Some(user) - }) - } else { - Ok(LoginResult { - result: false, - user: None - }) - } - }) - .await -} - -pub async fn get_user(conn: DbConn, id: Uuid) -> Result { - Ok(conn.run(move |c| users::table.filter(users::id.eq(id)).first(c)) - .await?) -} - -pub async fn get_all_known_games(conn: &DbConn) -> Result, DatabaseError> { - Ok(conn.run(|c| - known_games::table.load::(c) - ).await?) -} - -pub async fn add_game(conn: &DbConn, game: Game) -> Result { - Ok(conn.run(|c| - diesel::insert_into(known_games::table) - .values(game) - .execute(c) - - ).await?) -} - -pub async fn add_unknown_games(conn: &DbConn, games: &mut Vec) -> Result<(), DatabaseError> { - let all_games = get_all_known_games(conn).await?; - for game in games.iter_mut() { - if !all_games.iter().any(|g| g.name == game.name) { - game.id = Uuid::new_v4(); - add_game(conn, game.clone()).await?; - } - } - Ok(()) -} - - -pub fn unique_username( - username: &String, - conn: &diesel::PgConnection, -) -> Result<(), ValidationError> { - match users::table - .count() - .filter(users::username.eq(username)) - .get_result(conn) - { - Ok(0) => Ok(()), - Ok(_) => Err(ValidationError::new("User already exists")), - Err(_) => Err(ValidationError::new("Database error while validating user")), - } -} - -pub fn unique_email( - email: &String, - conn: &diesel::PgConnection, -) -> Result<(), ValidationError> { - match users::table - .count() - .filter(users::email.eq(email)) - .get_result(conn) - { - Ok(0) => Ok(()), - Ok(_) => Err(ValidationError::new("email already exists")), - Err(_) => Err(ValidationError::new( - "Database error while validating email", - )), - } -} - -pub async fn run_migrations(rocket: Rocket) -> Rocket { - // This macro from `diesel_migrations` defines an `embedded_migrations` - // module containing a function named `run`. This allows the example to be - // run and tested without any outside setup of the database. - embed_migrations!(); - - let conn = DbConn::get_one(&rocket).await.expect("database connection"); - conn.run(|c| embedded_migrations::run(c)) - .await - .expect("can run migrations"); - - rocket -} - -#[derive(Debug, Serialize, Deserialize, DbEnum, Clone, Copy, PartialEq)] -pub enum Role { - Admin, - User, -} - -#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] -#[table_name = "users"] -pub struct User { - pub id: Uuid, - pub username: String, - pub email: String, - pub role: Role, -} - -#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] -#[table_name = "pwd"] -struct Pwd { - user_id: Uuid, - password: String, -} - -#[derive(Serialize, Deserialize, Debug, Queryable, Clone, Insertable)] -#[table_name = "known_games"] -pub struct Game { - pub id: Uuid, - pub name: String, -} - -#[derive(Serialize, Deserialize, Debug, Queryable, Insertable)] -#[table_name = "gamenight"] -pub struct GameNight { - pub id: Uuid, - pub name: String, - pub datetime: String, - pub owner_id: Uuid, -} - -#[derive(Serialize, Deserialize, Debug, Queryable, Insertable)] -#[table_name="gamenight_gamelist"] -pub struct GamenightGameListEntry { - pub gamenight_id: Uuid, - pub game_id: Uuid -} - - -#[derive(Serialize, Deserialize, Debug, Queryable)] -pub struct DeleteGameNight { - pub game_id: Uuid, -} - -#[derive(Serialize, Deserialize, Debug, Validate, Clone)] -pub struct Register { - #[validate( - length(min = 1), - custom(function = "unique_username", arg = "&'v_a diesel::PgConnection") - )] - pub username: String, - #[validate( - email, - custom(function = "unique_email", arg = "&'v_a diesel::PgConnection") - )] - pub email: String, - #[validate(length(min = 10), must_match = "password_repeat")] - pub password: String, - pub password_repeat: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct Login { - pub username: String, - pub password: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct LoginResult { - pub result: bool, - pub user: Option, -} diff --git a/backend/src/schema/gamenight.rs b/backend/src/schema/gamenight.rs new file mode 100644 index 0000000..a69d190 --- /dev/null +++ b/backend/src/schema/gamenight.rs @@ -0,0 +1,161 @@ +use crate::schema::{DatabaseError, DbConn}; +use serde::{Deserialize, Serialize}; +use diesel::{QueryDsl, RunQueryDsl, Connection, ExpressionMethods}; +use uuid::Uuid; + +table! { + gamenight (id) { + id -> diesel::sql_types::Uuid, + name -> VarChar, + datetime -> VarChar, + owner_id -> Uuid, + } +} + +table! { + known_games (id) { + id -> diesel::sql_types::Uuid, + name -> VarChar, + } +} + +table! { + gamenight_gamelist(gamenight_id, game_id) { + gamenight_id -> diesel::sql_types::Uuid, + game_id -> diesel::sql_types::Uuid, + } +} + +table! { + gamenight_participants(gamenight_id, user_id) { + gamenight_id -> diesel::sql_types::Uuid, + user_id -> diesel::sql_types::Uuid, + } +} + +#[derive(Serialize, Deserialize, Debug, Queryable, Clone, Insertable)] +#[table_name = "known_games"] +pub struct Game { + pub id: Uuid, + pub name: String, +} + +#[derive(Serialize, Deserialize, Debug, Queryable, Insertable)] +#[table_name = "gamenight"] +pub struct GameNight { + pub id: Uuid, + pub name: String, + pub datetime: String, + pub owner_id: Uuid, +} + +#[derive(Serialize, Deserialize, Debug, Queryable, Insertable)] +#[table_name="gamenight_gamelist"] +pub struct GamenightGameListEntry { + pub gamenight_id: Uuid, + pub game_id: Uuid +} + +#[derive(Serialize, Deserialize, Debug, Queryable, Insertable, Identifiable)] +#[table_name="gamenight_participants"] +#[primary_key(gamenight_id, user_id)] +pub struct GamenightParticipantsEntry { + pub gamenight_id: Uuid, + pub user_id: Uuid +} + +#[derive(Serialize, Deserialize, Debug, Queryable)] +pub struct DeleteGameNight { + pub game_id: Uuid, +} + + +pub async fn get_all_gamenights(conn: DbConn) -> Result, DatabaseError> { + Ok(conn.run(|c| + gamenight::table.load::(c) + ).await?) +} + +pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNight, game_list: Vec) -> Result { + Ok(conn.run(move |c| + c.transaction(|| { + diesel::insert_into(gamenight::table) + .values(&new_gamenight) + .execute(c)?; + + let entries: Vec = game_list.iter().map( + |g| GamenightGameListEntry { gamenight_id: new_gamenight.id.clone(), game_id: g.id.clone() } + ).collect(); + + diesel::insert_into(gamenight_gamelist::table) + .values(entries) + .execute(c) + }) + ).await?) +} + +pub async fn get_gamenight(conn: &DbConn, game_id: Uuid) -> Result { + Ok(conn.run(move |c| + gamenight::table.find(game_id).first(c) + ).await?) +} + +pub async fn delete_gamenight(conn: &DbConn, game_id: Uuid) -> Result { + Ok(conn.run(move |c| + diesel::delete( + gamenight::table.filter( + gamenight::id.eq(game_id) + ) + ).execute(c) + ).await?) +} + + + +pub async fn get_all_known_games(conn: &DbConn) -> Result, DatabaseError> { + Ok(conn.run(|c| + known_games::table.load::(c) + ).await?) +} + +pub async fn add_game(conn: &DbConn, game: Game) -> Result { + Ok(conn.run(|c| + diesel::insert_into(known_games::table) + .values(game) + .execute(c) + + ).await?) +} + +pub async fn add_unknown_games(conn: &DbConn, games: &mut Vec) -> Result<(), DatabaseError> { + let all_games = get_all_known_games(conn).await?; + for game in games.iter_mut() { + if !all_games.iter().any(|g| g.name == game.name) { + game.id = Uuid::new_v4(); + add_game(conn, game.clone()).await?; + } + } + Ok(()) +} + +pub async fn insert_participant(conn: &DbConn, participant: GamenightParticipantsEntry) -> Result { + Ok(conn.run(move |c| + diesel::insert_into(gamenight_participants::table) + .values(&participant) + .execute(c) + ).await?) +} + +impl From for (Uuid, Uuid) { + + fn from(entry: GamenightParticipantsEntry) -> Self { + (entry.gamenight_id, entry.user_id) + } +} + +pub async fn remove_participant(conn: &DbConn, participant: GamenightParticipantsEntry) -> Result { + Ok(conn.run(move |c| + diesel::delete(&participant) + .execute(c) + ).await?) +} \ No newline at end of file diff --git a/backend/src/schema/mod.rs b/backend/src/schema/mod.rs new file mode 100644 index 0000000..c4170da --- /dev/null +++ b/backend/src/schema/mod.rs @@ -0,0 +1,60 @@ +pub mod users; +pub mod gamenight; + +use rocket::{Build, Rocket}; +use rocket_sync_db_pools::database; +use std::ops::Deref; + +#[database("gamenight_database")] +pub struct DbConn(diesel::PgConnection); + +impl Deref for DbConn { + type Target = rocket_sync_db_pools::Connection; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub enum DatabaseError { + Hash(password_hash::Error), + Query(String), +} + +pub async fn run_migrations(rocket: Rocket) -> Rocket { + // This macro from `diesel_migrations` defines an `embedded_migrations` + // module containing a function named `run`. This allows the example to be + // run and tested without any outside setup of the database. + embed_migrations!(); + + let conn = DbConn::get_one(&rocket).await.expect("database connection"); + conn.run(|c| embedded_migrations::run(c)) + .await + .expect("can run migrations"); + + rocket +} + +impl From for DatabaseError { + fn from(error: diesel::result::Error) -> Self { + Self::Query(error.to_string()) + } +} + +impl From for DatabaseError { + fn from(error: password_hash::Error) -> Self { + Self::Hash(error) + } +} + +impl std::fmt::Display for DatabaseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + match self { + DatabaseError::Hash(err) => write!(f, "{}", err), + DatabaseError::Query(err) => write!(f, "{}", err), + } + } +} + + + diff --git a/backend/src/schema/users.rs b/backend/src/schema/users.rs new file mode 100644 index 0000000..4a1a1cd --- /dev/null +++ b/backend/src/schema/users.rs @@ -0,0 +1,191 @@ +use validator::{Validate, ValidationError}; +use uuid::Uuid; +use diesel::{QueryDsl, RunQueryDsl, Connection, ExpressionMethods}; +use argon2::password_hash::SaltString; +use argon2::PasswordHash; +use argon2::PasswordVerifier; +use argon2::{ + password_hash::{rand_core::OsRng, PasswordHasher}, + Argon2, +}; +use diesel_derive_enum::DbEnum; +use serde::{Deserialize, Serialize}; +use crate::schema::{DbConn, DatabaseError}; + +#[derive(Debug, Serialize, Deserialize, DbEnum, Clone, Copy, PartialEq)] +pub enum Role { + Admin, + User, +} + +table! { + users(id) { + id -> diesel::sql_types::Uuid, + username -> VarChar, + email -> VarChar, + role -> crate::schema::users::RoleMapping, + } +} + +table! { + pwd(user_id) { + user_id -> diesel::sql_types::Uuid, + password -> VarChar, + } +} + +#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] +#[table_name = "pwd"] +struct Pwd { + user_id: Uuid, + password: String, +} + + +#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] +#[table_name = "users"] +pub struct User { + pub id: Uuid, + pub username: String, + pub email: String, + pub role: Role, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct UserWithToken { + #[serde(flatten)] + pub user: User, + pub jwt: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Login { + pub username: String, + pub password: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct LoginResult { + pub result: bool, + pub user: Option, +} + +#[derive(Serialize, Deserialize, Debug, Validate, Clone)] +pub struct Register { + #[validate( + length(min = 1), + custom(function = "unique_username", arg = "&'v_a diesel::PgConnection") + )] + pub username: String, + #[validate( + email, + custom(function = "unique_email", arg = "&'v_a diesel::PgConnection") + )] + pub email: String, + #[validate(length(min = 10), must_match = "password_repeat")] + pub password: String, + pub password_repeat: String, +} + +pub async fn insert_user(conn: DbConn, new_user: Register) -> Result { + let salt = SaltString::generate(&mut OsRng); + + let argon2 = Argon2::default(); + + let password_hash = argon2.hash_password(new_user.password.as_bytes(), &salt)?.to_string(); + + Ok(conn.run(move |c| { + c.transaction(|| { + let id = Uuid::new_v4(); + + diesel::insert_into(users::table) + .values(User { + id: id.clone(), + username: new_user.username, + email: new_user.email, + role: Role::User + }) + .execute(c)?; + + diesel::insert_into(pwd::table) + .values(Pwd { + user_id: id, + password: password_hash + }) + .execute(c) + }) + }) + .await?) +} + +pub async fn login(conn: DbConn, login: Login) -> Result { + conn.run(move |c| -> Result { + let id: Uuid = users::table + .filter(users::username.eq(&login.username)) + .or_filter(users::email.eq(&login.username)) + .select(users::id) + .first(c)?; + + let pwd: String = pwd::table + .filter(pwd::user_id.eq(id)) + .select(pwd::password) + .first(c)?; + + let parsed_hash = PasswordHash::new(&pwd)?; + + if Argon2::default() + .verify_password(&login.password.as_bytes(), &parsed_hash) + .is_ok() + { + let user: User = users::table.find(id).first(c)?; + + Ok(LoginResult { + result: true, + user : Some(user) + }) + } else { + Ok(LoginResult { + result: false, + user: None + }) + } + }) + .await +} + +pub async fn get_user(conn: DbConn, id: Uuid) -> Result { + Ok(conn.run(move |c| users::table.filter(users::id.eq(id)).first(c)) + .await?) +} + +pub fn unique_username( + username: &String, + conn: &diesel::PgConnection, +) -> Result<(), ValidationError> { + match users::table + .count() + .filter(users::username.eq(username)) + .get_result(conn) + { + Ok(0) => Ok(()), + Ok(_) => Err(ValidationError::new("User already exists")), + Err(_) => Err(ValidationError::new("Database error while validating user")), + } +} + +pub fn unique_email( + email: &String, + conn: &diesel::PgConnection, +) -> Result<(), ValidationError> { + match users::table + .count() + .filter(users::email.eq(email)) + .get_result(conn) + { + Ok(0) => Ok(()), + Ok(_) => Err(ValidationError::new("email already exists")), + Err(_) => Err(ValidationError::new( + "Database error while validating email", + )), + } +} \ No newline at end of file From 836a4ab59ffd57ee6bd566149e1c623f777d5cc3 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sat, 28 May 2022 18:32:00 +0200 Subject: [PATCH 2/7] Formatting commit. --- backend/src/api.rs | 79 ++++++++++----------- backend/src/main.rs | 8 +-- backend/src/schema/gamenight.rs | 118 +++++++++++++++++--------------- backend/src/schema/mod.rs | 7 +- backend/src/schema/users.rs | 58 ++++++++-------- backend/src/site.rs | 7 +- 6 files changed, 134 insertions(+), 143 deletions(-) diff --git a/backend/src/api.rs b/backend/src/api.rs index 17608fd..3f2a1a5 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -1,21 +1,16 @@ -use crate::schema::users::*; use crate::schema::gamenight::*; +use crate::schema::users::*; use crate::schema::DbConn; use crate::AppConfig; -use uuid::Uuid; use chrono::Utc; -use jsonwebtoken::decode; -use jsonwebtoken::encode; -use jsonwebtoken::DecodingKey; -use jsonwebtoken::Validation; -use jsonwebtoken::{EncodingKey, Header}; +use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use rocket::http::Status; -use rocket::request::Outcome; -use rocket::request::{FromRequest, Request}; +use rocket::request::{FromRequest, Outcome, Request}; use rocket::serde::json::{json, Json, Value}; use rocket::State; use serde::{Deserialize, Serialize}; use std::borrow::Cow; +use uuid::Uuid; use validator::ValidateArgs; #[derive(Debug, Responder)] @@ -65,7 +60,7 @@ impl ApiResponse { message: None, user: Some(UserWithToken { user: user, - jwt: jwt + jwt: jwt, }), gamenights: None, games: None, @@ -108,9 +103,7 @@ impl<'r> FromRequest<'r> for User { async fn from_request(req: &'r Request<'_>) -> Outcome { let header = match req.headers().get_one(AUTH_HEADER) { Some(header) => header, - None => { - return Outcome::Forward(()) - } + None => return Outcome::Forward(()), }; if !header.starts_with(BEARER) { @@ -125,25 +118,25 @@ impl<'r> FromRequest<'r> for User { &Validation::default(), ) { Ok(token) => token, - Err(_) => { - return Outcome::Forward(()) - } + Err(_) => return Outcome::Forward(()), }; let id = token.claims.uid; let conn = req.guard::().await.unwrap(); return match get_user(conn, id).await { Ok(o) => Outcome::Success(o), - Err(_) => Outcome::Forward(()) - } + Err(_) => Outcome::Forward(()), + }; } } #[get("/gamenights")] pub async fn gamenights(conn: DbConn, _user: User) -> ApiResponseVariant { match get_all_gamenights(conn).await { - Ok(gamenights) => ApiResponseVariant::Value(json!(ApiResponse::gamenight_response(gamenights))), - Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) + Ok(gamenights) => { + ApiResponseVariant::Value(json!(ApiResponse::gamenight_response(gamenights))) + } + Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))), } } @@ -161,13 +154,12 @@ pub struct GameNightInput { } impl Into for GameNightInput { - - fn into(self) -> GameNight { + fn into(self) -> GameNight { GameNight { id: Uuid::new_v4(), name: self.name, datetime: self.datetime, - owner_id: self.owner_id.unwrap() + owner_id: self.owner_id.unwrap(), } } } @@ -185,12 +177,12 @@ pub async fn gamenights_post_json( match add_unknown_games(&conn, &mut mutable_game_list).await { Ok(_) => (), - Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) + Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), }; match insert_gamenight(conn, gamenight.clone().into(), mutable_game_list).await { Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), - Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) + Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), } } @@ -199,44 +191,46 @@ pub async fn gamenights_post_json_unauthorized() -> ApiResponseVariant { ApiResponseVariant::Status(Status::Unauthorized) } -#[delete("/gamenights", format = "application/json", data = "")] +#[delete( + "/gamenights", + format = "application/json", + data = "" +)] pub async fn gamenights_delete_json( conn: DbConn, user: User, - delete_gamenight_json: Json + delete_gamenight_json: Json, ) -> ApiResponseVariant { if user.role == Role::Admin { if let Err(error) = delete_gamenight(&conn, delete_gamenight_json.game_id).await { - return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) + return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))); } - return ApiResponseVariant::Value(json!(ApiResponse::SUCCES)) + return ApiResponseVariant::Value(json!(ApiResponse::SUCCES)); } - + match get_gamenight(&conn, delete_gamenight_json.game_id).await { Ok(gamenight) => { if user.id == gamenight.owner_id { if let Err(error) = delete_gamenight(&conn, delete_gamenight_json.game_id).await { - return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) + return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))); } - return ApiResponseVariant::Value(json!(ApiResponse::SUCCES)) + return ApiResponseVariant::Value(json!(ApiResponse::SUCCES)); } - }, - Err(error) => return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) + } + Err(error) => { + return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) + } } ApiResponseVariant::Status(Status::Unauthorized) } - #[delete("/gamenights", rank = 2)] pub async fn gamenights_delete_json_unauthorized() -> ApiResponseVariant { ApiResponseVariant::Status(Status::Unauthorized) } #[post("/register", format = "application/json", data = "")] -pub async fn register_post_json( - conn: DbConn, - register_json: Json, -) -> ApiResponseVariant { +pub async fn register_post_json(conn: DbConn, register_json: Json) -> ApiResponseVariant { let register = register_json.into_inner(); let register_clone = register.clone(); match conn @@ -290,7 +284,9 @@ pub async fn login_post_json( &my_claims, &EncodingKey::from_secret(secret.as_bytes()), ) { - Ok(token) => ApiResponseVariant::Value(json!(ApiResponse::login_response(user, token))), + Ok(token) => { + ApiResponseVariant::Value(json!(ApiResponse::login_response(user, token))) + } Err(error) => { ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) } @@ -303,7 +299,7 @@ pub async fn login_post_json( pub async fn games(conn: DbConn, _user: User) -> ApiResponseVariant { match get_all_known_games(&conn).await { Ok(games) => ApiResponseVariant::Value(json!(ApiResponse::games_response(games))), - Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) + Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))), } } @@ -311,4 +307,3 @@ pub async fn games(conn: DbConn, _user: User) -> ApiResponseVariant { pub async fn games_unauthorized() -> ApiResponseVariant { ApiResponseVariant::Status(Status::Unauthorized) } - diff --git a/backend/src/main.rs b/backend/src/main.rs index 3b4215f..cc9989e 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -46,13 +46,7 @@ async fn rocket() -> _ { .attach(AdHoc::on_ignite("Run Migrations", schema::run_migrations)) .attach(AdHoc::config::()) .attach(site::make_cors()) - .mount( - "/", - routes![ - site::index, - site::files - ], - ) + .mount("/", routes![site::index, site::files]) .mount( "/api", routes![ diff --git a/backend/src/schema/gamenight.rs b/backend/src/schema/gamenight.rs index a69d190..ba8b5b3 100644 --- a/backend/src/schema/gamenight.rs +++ b/backend/src/schema/gamenight.rs @@ -1,6 +1,6 @@ use crate::schema::{DatabaseError, DbConn}; +use diesel::{Connection, ExpressionMethods, QueryDsl, RunQueryDsl}; use serde::{Deserialize, Serialize}; -use diesel::{QueryDsl, RunQueryDsl, Connection, ExpressionMethods}; use uuid::Uuid; table! { @@ -50,18 +50,18 @@ pub struct GameNight { } #[derive(Serialize, Deserialize, Debug, Queryable, Insertable)] -#[table_name="gamenight_gamelist"] +#[table_name = "gamenight_gamelist"] pub struct GamenightGameListEntry { pub gamenight_id: Uuid, - pub game_id: Uuid + pub game_id: Uuid, } #[derive(Serialize, Deserialize, Debug, Queryable, Insertable, Identifiable)] -#[table_name="gamenight_participants"] +#[table_name = "gamenight_participants"] #[primary_key(gamenight_id, user_id)] pub struct GamenightParticipantsEntry { pub gamenight_id: Uuid, - pub user_id: Uuid + pub user_id: Uuid, } #[derive(Serialize, Deserialize, Debug, Queryable)] @@ -69,62 +69,62 @@ pub struct DeleteGameNight { pub game_id: Uuid, } - pub async fn get_all_gamenights(conn: DbConn) -> Result, DatabaseError> { - Ok(conn.run(|c| - gamenight::table.load::(c) - ).await?) + Ok(conn.run(|c| gamenight::table.load::(c)).await?) } -pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNight, game_list: Vec) -> Result { - Ok(conn.run(move |c| - c.transaction(|| { - diesel::insert_into(gamenight::table) - .values(&new_gamenight) - .execute(c)?; +pub async fn insert_gamenight( + conn: DbConn, + new_gamenight: GameNight, + game_list: Vec, +) -> Result { + Ok(conn + .run(move |c| { + c.transaction(|| { + diesel::insert_into(gamenight::table) + .values(&new_gamenight) + .execute(c)?; - let entries: Vec = game_list.iter().map( - |g| GamenightGameListEntry { gamenight_id: new_gamenight.id.clone(), game_id: g.id.clone() } - ).collect(); + let entries: Vec = game_list + .iter() + .map(|g| GamenightGameListEntry { + gamenight_id: new_gamenight.id.clone(), + game_id: g.id.clone(), + }) + .collect(); - diesel::insert_into(gamenight_gamelist::table) - .values(entries) - .execute(c) + diesel::insert_into(gamenight_gamelist::table) + .values(entries) + .execute(c) + }) }) - ).await?) + .await?) } pub async fn get_gamenight(conn: &DbConn, game_id: Uuid) -> Result { - Ok(conn.run(move |c| - gamenight::table.find(game_id).first(c) - ).await?) + Ok(conn + .run(move |c| gamenight::table.find(game_id).first(c)) + .await?) } pub async fn delete_gamenight(conn: &DbConn, game_id: Uuid) -> Result { - Ok(conn.run(move |c| - diesel::delete( - gamenight::table.filter( - gamenight::id.eq(game_id) - ) - ).execute(c) - ).await?) + Ok(conn + .run(move |c| diesel::delete(gamenight::table.filter(gamenight::id.eq(game_id))).execute(c)) + .await?) } - - pub async fn get_all_known_games(conn: &DbConn) -> Result, DatabaseError> { - Ok(conn.run(|c| - known_games::table.load::(c) - ).await?) + Ok(conn.run(|c| known_games::table.load::(c)).await?) } pub async fn add_game(conn: &DbConn, game: Game) -> Result { - Ok(conn.run(|c| - diesel::insert_into(known_games::table) - .values(game) - .execute(c) - - ).await?) + Ok(conn + .run(|c| { + diesel::insert_into(known_games::table) + .values(game) + .execute(c) + }) + .await?) } pub async fn add_unknown_games(conn: &DbConn, games: &mut Vec) -> Result<(), DatabaseError> { @@ -138,24 +138,30 @@ pub async fn add_unknown_games(conn: &DbConn, games: &mut Vec) -> Result<( Ok(()) } -pub async fn insert_participant(conn: &DbConn, participant: GamenightParticipantsEntry) -> Result { - Ok(conn.run(move |c| - diesel::insert_into(gamenight_participants::table) - .values(&participant) - .execute(c) - ).await?) +pub async fn insert_participant( + conn: &DbConn, + participant: GamenightParticipantsEntry, +) -> Result { + Ok(conn + .run(move |c| { + diesel::insert_into(gamenight_participants::table) + .values(&participant) + .execute(c) + }) + .await?) } impl From for (Uuid, Uuid) { - fn from(entry: GamenightParticipantsEntry) -> Self { (entry.gamenight_id, entry.user_id) } } -pub async fn remove_participant(conn: &DbConn, participant: GamenightParticipantsEntry) -> Result { - Ok(conn.run(move |c| - diesel::delete(&participant) - .execute(c) - ).await?) -} \ No newline at end of file +pub async fn remove_participant( + conn: &DbConn, + participant: GamenightParticipantsEntry, +) -> Result { + Ok(conn + .run(move |c| diesel::delete(&participant).execute(c)) + .await?) +} diff --git a/backend/src/schema/mod.rs b/backend/src/schema/mod.rs index c4170da..60598b5 100644 --- a/backend/src/schema/mod.rs +++ b/backend/src/schema/mod.rs @@ -1,5 +1,5 @@ -pub mod users; pub mod gamenight; +pub mod users; use rocket::{Build, Rocket}; use rocket_sync_db_pools::database; @@ -36,7 +36,7 @@ pub async fn run_migrations(rocket: Rocket) -> Rocket { } impl From for DatabaseError { - fn from(error: diesel::result::Error) -> Self { + fn from(error: diesel::result::Error) -> Self { Self::Query(error.to_string()) } } @@ -55,6 +55,3 @@ impl std::fmt::Display for DatabaseError { } } } - - - diff --git a/backend/src/schema/users.rs b/backend/src/schema/users.rs index 4a1a1cd..a29ca50 100644 --- a/backend/src/schema/users.rs +++ b/backend/src/schema/users.rs @@ -1,6 +1,4 @@ -use validator::{Validate, ValidationError}; -use uuid::Uuid; -use diesel::{QueryDsl, RunQueryDsl, Connection, ExpressionMethods}; +use crate::schema::{DatabaseError, DbConn}; use argon2::password_hash::SaltString; use argon2::PasswordHash; use argon2::PasswordVerifier; @@ -8,9 +6,11 @@ use argon2::{ password_hash::{rand_core::OsRng, PasswordHasher}, Argon2, }; +use diesel::{Connection, ExpressionMethods, QueryDsl, RunQueryDsl}; use diesel_derive_enum::DbEnum; use serde::{Deserialize, Serialize}; -use crate::schema::{DbConn, DatabaseError}; +use uuid::Uuid; +use validator::{Validate, ValidationError}; #[derive(Debug, Serialize, Deserialize, DbEnum, Clone, Copy, PartialEq)] pub enum Role { @@ -41,7 +41,6 @@ struct Pwd { password: String, } - #[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] #[table_name = "users"] pub struct User { @@ -92,30 +91,33 @@ pub async fn insert_user(conn: DbConn, new_user: Register) -> Result Result { @@ -141,12 +143,12 @@ pub async fn login(conn: DbConn, login: Login) -> Result Result Result { - Ok(conn.run(move |c| users::table.filter(users::id.eq(id)).first(c)) + Ok(conn + .run(move |c| users::table.filter(users::id.eq(id)).first(c)) .await?) } @@ -173,10 +176,7 @@ pub fn unique_username( } } -pub fn unique_email( - email: &String, - conn: &diesel::PgConnection, -) -> Result<(), ValidationError> { +pub fn unique_email(email: &String, conn: &diesel::PgConnection) -> Result<(), ValidationError> { match users::table .count() .filter(users::email.eq(email)) @@ -188,4 +188,4 @@ pub fn unique_email( "Database error while validating email", )), } -} \ No newline at end of file +} diff --git a/backend/src/site.rs b/backend/src/site.rs index 7f47ca2..0d8193d 100644 --- a/backend/src/site.rs +++ b/backend/src/site.rs @@ -1,20 +1,19 @@ +use local_ip_address::local_ip; use rocket::fs::NamedFile; use rocket::http::Method; use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors, CorsOptions}; use std::io; use std::path::{Path, PathBuf}; -use local_ip_address::local_ip; - pub fn make_cors() -> Cors { let allowed_origins = AllowedOrigins::some_exact(&[ "http://localhost:3000", "http://127.0.0.1:3000", - &format!("http://{}:8000",local_ip().unwrap())[..], + &format!("http://{}:8000", local_ip().unwrap())[..], "http://localhost:8000", "http://0.0.0.0:8000", ]); - + CorsOptions { allowed_origins, allowed_methods: vec![Method::Get].into_iter().map(From::from).collect(), // 1. From 86cdbedd4143a5e18e14b72318497a49edb25bc0 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sat, 28 May 2022 19:28:58 +0200 Subject: [PATCH 3/7] Adds the participants part of the API. --- backend/src/api.rs | 48 ++++++++++++++++++++++++++++++++- backend/src/main.rs | 6 +++++ backend/src/schema/gamenight.rs | 34 +++++++++++++++++------ 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/backend/src/api.rs b/backend/src/api.rs index 3f2a1a5..6bb69cc 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -180,7 +180,13 @@ pub async fn gamenights_post_json( Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), }; - match insert_gamenight(conn, gamenight.clone().into(), mutable_game_list).await { + let gamenight_id = match insert_gamenight(&conn, gamenight.clone().into(), mutable_game_list).await { + Ok(id) => id, + Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), + }; + + let participant = GamenightParticipantsEntry{gamenight_id: gamenight_id, user_id: user.id}; + match insert_participant(&conn, participant).await { Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), } @@ -307,3 +313,43 @@ pub async fn games(conn: DbConn, _user: User) -> ApiResponseVariant { pub async fn games_unauthorized() -> ApiResponseVariant { ApiResponseVariant::Status(Status::Unauthorized) } + +#[get("/participants", format = "application/json", data = "")] +pub async fn get_participants(conn: DbConn, _user: User, gamenight_id_json: Json) -> ApiResponseVariant { + match load_participants(&conn, gamenight_id_json.into_inner()).await { + Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), + Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))), + } +} + +#[get("/participants", rank = 2)] +pub async fn get_participants_unauthorized() -> ApiResponseVariant { + ApiResponseVariant::Status(Status::Unauthorized) +} + +#[post("/participants", format = "application/json", data = "")] +pub async fn post_participants(conn: DbConn, _user: User, entry_json: Json) -> ApiResponseVariant { + match insert_participant(&conn, entry_json.into_inner()).await { + Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), + Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))), + } +} + +#[post("/participants", rank = 2)] +pub async fn post_participants_unauthorized() -> ApiResponseVariant { + ApiResponseVariant::Status(Status::Unauthorized) +} + +#[delete("/participants", format = "application/json", data = "")] +pub async fn delete_participants(conn: DbConn, _user: User, entry_json: Json) -> ApiResponseVariant { + match remove_participant(&conn, entry_json.into_inner()).await { + Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), + Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))), + } +} + +#[delete("/participants", rank = 2)] +pub async fn delete_participants_unauthorized() -> ApiResponseVariant { + ApiResponseVariant::Status(Status::Unauthorized) +} + diff --git a/backend/src/main.rs b/backend/src/main.rs index cc9989e..2e1c2b0 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -60,6 +60,12 @@ async fn rocket() -> _ { api::gamenights_delete_json_unauthorized, api::games, api::games_unauthorized, + api::get_participants, + api::get_participants_unauthorized, + api::post_participants, + api::post_participants_unauthorized, + api::delete_participants, + api::delete_participants_unauthorized, ], ); diff --git a/backend/src/schema/gamenight.rs b/backend/src/schema/gamenight.rs index ba8b5b3..1a3d417 100644 --- a/backend/src/schema/gamenight.rs +++ b/backend/src/schema/gamenight.rs @@ -69,21 +69,27 @@ pub struct DeleteGameNight { pub game_id: Uuid, } +#[derive(Serialize, Deserialize, Debug, Queryable)] +pub struct GamenightId { + pub gamenight_id: Uuid, +} + pub async fn get_all_gamenights(conn: DbConn) -> Result, DatabaseError> { Ok(conn.run(|c| gamenight::table.load::(c)).await?) } pub async fn insert_gamenight( - conn: DbConn, + conn: &DbConn, new_gamenight: GameNight, game_list: Vec, -) -> Result { +) -> Result { Ok(conn .run(move |c| { - c.transaction(|| { - diesel::insert_into(gamenight::table) + c.transaction::<_, DatabaseError, _>(|| { + let id : Uuid = diesel::insert_into(gamenight::table) .values(&new_gamenight) - .execute(c)?; + .returning(gamenight::id) + .get_result(c)?; let entries: Vec = game_list .iter() @@ -95,10 +101,12 @@ pub async fn insert_gamenight( diesel::insert_into(gamenight_gamelist::table) .values(entries) - .execute(c) + .execute(c)?; + + Ok(id) }) - }) - .await?) + }).await? + ) } pub async fn get_gamenight(conn: &DbConn, game_id: Uuid) -> Result { @@ -138,6 +146,16 @@ pub async fn add_unknown_games(conn: &DbConn, games: &mut Vec) -> Result<( Ok(()) } +pub async fn load_participants(conn: &DbConn, gamenight_id: GamenightId) -> Result, DatabaseError> { + Ok(conn + .run(move |c| { + gamenight_participants::table + .filter(gamenight_participants::gamenight_id.eq(gamenight_id.gamenight_id)) + .load::(c) + }) + .await?) +} + pub async fn insert_participant( conn: &DbConn, participant: GamenightParticipantsEntry, From 2ba2026e212f17d41847e146014605a6d98e240b Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sun, 29 May 2022 00:22:30 +0200 Subject: [PATCH 4/7] Gamenights also return their game list. --- backend/Cargo.lock | 25 +++++++++++++ backend/Cargo.toml | 3 +- backend/src/api.rs | 50 ++++++++++++++++++-------- backend/src/schema/gamenight.rs | 28 +++++++++++---- frontend/src/App.js | 31 +++++++++++++--- frontend/src/components/Gamenight.jsx | 27 ++++++++++++++ frontend/src/components/Gamenights.jsx | 2 +- 7 files changed, 139 insertions(+), 27 deletions(-) create mode 100644 frontend/src/components/Gamenight.jsx diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f456cc9..cd953d3 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -518,6 +518,7 @@ checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -540,12 +541,34 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.21" @@ -567,6 +590,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -584,6 +608,7 @@ dependencies = [ "diesel", "diesel-derive-enum", "diesel_migrations", + "futures", "jsonwebtoken", "local-ip-address", "password-hash", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index ca9cf16..8d7c32d 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -22,4 +22,5 @@ jsonwebtoken = "8.1" validator = { version = "0.14", features = ["derive"] } rocket_cors = "0.6.0-alpha1" local-ip-address = "0.4" -uuid = { version = "0.8.2", features = ["v4", "serde"] } \ No newline at end of file +uuid = { version = "0.8.2", features = ["v4", "serde"] } +futures = "0.3.21" \ No newline at end of file diff --git a/backend/src/api.rs b/backend/src/api.rs index 6bb69cc..260fea7 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -1,3 +1,4 @@ +use crate::schema::DatabaseError; use crate::schema::gamenight::*; use crate::schema::users::*; use crate::schema::DbConn; @@ -12,6 +13,7 @@ use serde::{Deserialize, Serialize}; use std::borrow::Cow; use uuid::Uuid; use validator::ValidateArgs; +use futures::future::join_all; #[derive(Debug, Responder)] pub enum ApiResponseVariant { @@ -27,7 +29,7 @@ struct ApiResponse { #[serde(skip_serializing_if = "Option::is_none")] user: Option, #[serde(skip_serializing_if = "Option::is_none")] - gamenights: Option>, + gamenights: Option>, #[serde(skip_serializing_if = "Option::is_none")] games: Option>, } @@ -67,7 +69,7 @@ impl ApiResponse { } } - fn gamenight_response(gamenights: Vec) -> Self { + fn gamenight_response(gamenights: Vec) -> Self { Self { result: Self::SUCCES_RESULT, message: None, @@ -130,14 +132,34 @@ impl<'r> FromRequest<'r> for User { } } +#[derive(Debug, Serialize, Deserialize)] +pub struct GamenightOutput { + #[serde(flatten)] + gamenight: Gamenight, + game_list: Vec, +} + #[get("/gamenights")] pub async fn gamenights(conn: DbConn, _user: User) -> ApiResponseVariant { - match get_all_gamenights(conn).await { - Ok(gamenights) => { - ApiResponseVariant::Value(json!(ApiResponse::gamenight_response(gamenights))) - } - Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))), - } + let gamenights = match get_all_gamenights(&conn).await { + Ok(result) => result, + Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) + }; + + let conn_ref = &conn; + + let game_results : Result, DatabaseError> = join_all(gamenights.iter().map(|gn| async move { + let games = get_games_of_gamenight(conn_ref, gn.id).await?; + Ok(GamenightOutput{ + gamenight: gn.clone(), + game_list: games + }) + })).await.into_iter().collect(); + + match game_results { + Ok(result) => ApiResponseVariant::Value(json!(ApiResponse::gamenight_response(result))), + Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) + } } #[get("/gamenights", rank = 2)] @@ -146,16 +168,16 @@ pub async fn gamenights_unauthorized() -> ApiResponseVariant { } #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct GameNightInput { +pub struct GamenightInput { pub name: String, pub datetime: String, pub owner_id: Option, pub game_list: Vec, } -impl Into for GameNightInput { - fn into(self) -> GameNight { - GameNight { +impl Into for GamenightInput { + fn into(self) -> Gamenight { + Gamenight { id: Uuid::new_v4(), name: self.name, datetime: self.datetime, @@ -168,7 +190,7 @@ impl Into for GameNightInput { pub async fn gamenights_post_json( conn: DbConn, user: User, - gamenight_json: Json, + gamenight_json: Json, ) -> ApiResponseVariant { let mut gamenight = gamenight_json.into_inner(); gamenight.owner_id = Some(user.id); @@ -205,7 +227,7 @@ pub async fn gamenights_post_json_unauthorized() -> ApiResponseVariant { pub async fn gamenights_delete_json( conn: DbConn, user: User, - delete_gamenight_json: Json, + delete_gamenight_json: Json, ) -> ApiResponseVariant { if user.role == Role::Admin { if let Err(error) = delete_gamenight(&conn, delete_gamenight_json.game_id).await { diff --git a/backend/src/schema/gamenight.rs b/backend/src/schema/gamenight.rs index 1a3d417..5177515 100644 --- a/backend/src/schema/gamenight.rs +++ b/backend/src/schema/gamenight.rs @@ -40,9 +40,9 @@ pub struct Game { pub name: String, } -#[derive(Serialize, Deserialize, Debug, Queryable, Insertable)] +#[derive(Serialize, Deserialize, Debug, Queryable, Insertable, Clone)] #[table_name = "gamenight"] -pub struct GameNight { +pub struct Gamenight { pub id: Uuid, pub name: String, pub datetime: String, @@ -65,7 +65,7 @@ pub struct GamenightParticipantsEntry { } #[derive(Serialize, Deserialize, Debug, Queryable)] -pub struct DeleteGameNight { +pub struct DeleteGamenight { pub game_id: Uuid, } @@ -74,13 +74,13 @@ pub struct GamenightId { pub gamenight_id: Uuid, } -pub async fn get_all_gamenights(conn: DbConn) -> Result, DatabaseError> { - Ok(conn.run(|c| gamenight::table.load::(c)).await?) +pub async fn get_all_gamenights(conn: &DbConn) -> Result, DatabaseError> { + Ok(conn.run(|c| gamenight::table.load::(c)).await?) } pub async fn insert_gamenight( conn: &DbConn, - new_gamenight: GameNight, + new_gamenight: Gamenight, game_list: Vec, ) -> Result { Ok(conn @@ -109,7 +109,7 @@ pub async fn insert_gamenight( ) } -pub async fn get_gamenight(conn: &DbConn, game_id: Uuid) -> Result { +pub async fn get_gamenight(conn: &DbConn, game_id: Uuid) -> Result { Ok(conn .run(move |c| gamenight::table.find(game_id).first(c)) .await?) @@ -125,6 +125,20 @@ pub async fn get_all_known_games(conn: &DbConn) -> Result, DatabaseErr Ok(conn.run(|c| known_games::table.load::(c)).await?) } +pub async fn get_games_of_gamenight(conn: &DbConn, gamenight_id: Uuid) -> Result, DatabaseError> { + Ok(conn.run::<_, Result, _>>(move |c| { + let linked_game_ids: Vec = gamenight_gamelist::table + .filter(gamenight_gamelist::gamenight_id.eq(gamenight_id)) + .load::(c)?; + + linked_game_ids.iter().map(|l| { + known_games::table + .filter(known_games::id.eq(l.game_id)) + .first::(c) + }).collect() + }).await?) +} + pub async fn add_game(conn: &DbConn, game: Game) -> Result { Ok(conn .run(|c| { diff --git a/frontend/src/App.js b/frontend/src/App.js index f1f8416..f710b24 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -2,8 +2,9 @@ import './App.css'; import React, { useState, useEffect } from 'react'; import MenuBar from './components/MenuBar'; import Login from './components/Login'; -import Gamenights from './components/Gamenights' -import AddGameNight from './components/AddGameNight' +import Gamenights from './components/Gamenights'; +import AddGameNight from './components/AddGameNight'; +import Gamenight from './components/Gamenight'; const localStorageUserKey = 'user'; @@ -13,6 +14,7 @@ function App() { const [gamenights, setGamenights] = useState([]); const [flashData, setFlashData] = useState({}); const [games, setGames] = useState([]); + const [activeGamenight, setActiveGamenight] = useState(null); const handleLogin = (input) => { const requestOptions = { @@ -94,6 +96,9 @@ function App() { setUser(JSON.parse(localStorage.getItem(localStorageUserKey))); }, []); + + console.log(activeGamenight); + if(user === null) { return (
@@ -101,11 +106,29 @@ function App() {
); } else { + + let mainview; + if(activeGamenight === null) { + mainview = <> + + + + } else { + mainview = + } + return ( <> - - + {mainview} ); } diff --git a/frontend/src/components/Gamenight.jsx b/frontend/src/components/Gamenight.jsx new file mode 100644 index 0000000..35f688f --- /dev/null +++ b/frontend/src/components/Gamenight.jsx @@ -0,0 +1,27 @@ +import * as React from 'react'; + +function Gamenight(props) { + + console.log(props.gamenight); + + let games = props.gamenight.game_list.map(g => + ( +
  • + {g.name} +
  • + ) + ); + + return ( +
    + +

    {props.gamenight.name}

    + {props.gamenight.datetime} +
      + {games} +
    +
    + ) +} + +export default Gamenight diff --git a/frontend/src/components/Gamenights.jsx b/frontend/src/components/Gamenights.jsx index 3a5d35a..de6d76a 100644 --- a/frontend/src/components/Gamenights.jsx +++ b/frontend/src/components/Gamenights.jsx @@ -33,7 +33,7 @@ function Gamenights(props) { let gamenights = props.gamenights.map(g => ( -
  • +
  • {console.log(g); props.setActiveGamenight(g);}}> {g.name} {(props.user.id === g.owner_id || props.user.role === "Admin") && -

    {props.gamenight.name}

    - {props.gamenight.datetime} -
      - {games} -
    - +
    +

    {props.gamenight.name}

    + + {props.gamenight.datetime} +

    Games:

    +
      + {games} +
    +

    Participants:

    +
      + {participants} +
    +
    ) } diff --git a/frontend/src/components/Gamenights.jsx b/frontend/src/components/Gamenights.jsx index de6d76a..0f843a2 100644 --- a/frontend/src/components/Gamenights.jsx +++ b/frontend/src/components/Gamenights.jsx @@ -29,16 +29,14 @@ function Gamenights(props) { }) .then(() => props.refetchGamenights()); } - } + } let gamenights = props.gamenights.map(g => ( -
  • {console.log(g); props.setActiveGamenight(g);}}> +
  • props.onSelectGamenight(g)}> {g.name} {(props.user.id === g.owner_id || props.user.role === "Admin") && - + }
  • ) From 102a3e6082fec8c8a5baecb3b9744ec708d4fed6 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sun, 29 May 2022 10:33:55 +0200 Subject: [PATCH 6/7] Formatting commit --- backend/src/api.rs | 67 ++++++++++++++++++++++----------- backend/src/schema/gamenight.rs | 56 ++++++++++++++++----------- 2 files changed, 80 insertions(+), 43 deletions(-) diff --git a/backend/src/api.rs b/backend/src/api.rs index 92f186e..fc30e6d 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -1,9 +1,10 @@ -use crate::schema::DatabaseError; use crate::schema::gamenight::*; use crate::schema::users::*; +use crate::schema::DatabaseError; use crate::schema::DbConn; use crate::AppConfig; use chrono::Utc; +use futures::future::join_all; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; use rocket::http::Status; use rocket::request::{FromRequest, Outcome, Request}; @@ -13,7 +14,6 @@ use serde::{Deserialize, Serialize}; use std::borrow::Cow; use uuid::Uuid; use validator::ValidateArgs; -use futures::future::join_all; #[derive(Debug, Responder)] pub enum ApiResponseVariant { @@ -144,26 +144,29 @@ pub struct GamenightOutput { pub async fn gamenights(conn: DbConn, _user: User) -> ApiResponseVariant { let gamenights = match get_all_gamenights(&conn).await { Ok(result) => result, - Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) + Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), }; let conn_ref = &conn; - let game_results : Result, DatabaseError> = join_all(gamenights.iter().map(|gn| async move { - let games = get_games_of_gamenight(conn_ref, gn.id).await?; - let participants = load_participants(conn_ref, gn.id).await?; - Ok(GamenightOutput{ - gamenight: gn.clone(), - game_list: games, - participants: participants, - }) - })).await.into_iter().collect(); - + let game_results: Result, DatabaseError> = + join_all(gamenights.iter().map(|gn| async move { + let games = get_games_of_gamenight(conn_ref, gn.id).await?; + let participants = load_participants(conn_ref, gn.id).await?; + Ok(GamenightOutput { + gamenight: gn.clone(), + game_list: games, + participants: participants, + }) + })) + .await + .into_iter() + .collect(); match game_results { Ok(result) => ApiResponseVariant::Value(json!(ApiResponse::gamenight_response(result))), - Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) - } + Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), + } } #[get("/gamenights", rank = 2)] @@ -206,12 +209,17 @@ pub async fn gamenights_post_json( Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), }; - let gamenight_id = match insert_gamenight(&conn, gamenight.clone().into(), mutable_game_list).await { + let gamenight_id = match insert_gamenight(&conn, gamenight.clone().into(), mutable_game_list) + .await + { Ok(id) => id, Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), }; - let participant = GamenightParticipantsEntry{gamenight_id: gamenight_id, user_id: user.id}; + let participant = GamenightParticipantsEntry { + gamenight_id: gamenight_id, + user_id: user.id, + }; match insert_participant(&conn, participant).await { Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), @@ -340,8 +348,16 @@ pub async fn games_unauthorized() -> ApiResponseVariant { ApiResponseVariant::Status(Status::Unauthorized) } -#[get("/participants", format = "application/json", data = "")] -pub async fn get_participants(conn: DbConn, _user: User, gamenight_id_json: Json) -> ApiResponseVariant { +#[get( + "/participants", + format = "application/json", + data = "" +)] +pub async fn get_participants( + conn: DbConn, + _user: User, + gamenight_id_json: Json, +) -> ApiResponseVariant { match load_participants(&conn, gamenight_id_json.into_inner().gamenight_id).await { Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))), @@ -354,7 +370,11 @@ pub async fn get_participants_unauthorized() -> ApiResponseVariant { } #[post("/participants", format = "application/json", data = "")] -pub async fn post_participants(conn: DbConn, _user: User, entry_json: Json) -> ApiResponseVariant { +pub async fn post_participants( + conn: DbConn, + _user: User, + entry_json: Json, +) -> ApiResponseVariant { match insert_participant(&conn, entry_json.into_inner()).await { Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))), @@ -367,7 +387,11 @@ pub async fn post_participants_unauthorized() -> ApiResponseVariant { } #[delete("/participants", format = "application/json", data = "")] -pub async fn delete_participants(conn: DbConn, _user: User, entry_json: Json) -> ApiResponseVariant { +pub async fn delete_participants( + conn: DbConn, + _user: User, + entry_json: Json, +) -> ApiResponseVariant { match remove_participant(&conn, entry_json.into_inner()).await { Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))), @@ -378,4 +402,3 @@ pub async fn delete_participants(conn: DbConn, _user: User, entry_json: Json ApiResponseVariant { ApiResponseVariant::Status(Status::Unauthorized) } - diff --git a/backend/src/schema/gamenight.rs b/backend/src/schema/gamenight.rs index 754bd11..3cc1c0a 100644 --- a/backend/src/schema/gamenight.rs +++ b/backend/src/schema/gamenight.rs @@ -87,7 +87,7 @@ pub async fn insert_gamenight( Ok(conn .run(move |c| { c.transaction::<_, DatabaseError, _>(|| { - let id : Uuid = diesel::insert_into(gamenight::table) + let id: Uuid = diesel::insert_into(gamenight::table) .values(&new_gamenight) .returning(gamenight::id) .get_result(c)?; @@ -106,8 +106,8 @@ pub async fn insert_gamenight( Ok(id) }) - }).await? - ) + }) + .await?) } pub async fn get_gamenight(conn: &DbConn, game_id: Uuid) -> Result { @@ -126,18 +126,26 @@ pub async fn get_all_known_games(conn: &DbConn) -> Result, DatabaseErr Ok(conn.run(|c| known_games::table.load::(c)).await?) } -pub async fn get_games_of_gamenight(conn: &DbConn, gamenight_id: Uuid) -> Result, DatabaseError> { - Ok(conn.run::<_, Result, _>>(move |c| { - let linked_game_ids: Vec = gamenight_gamelist::table - .filter(gamenight_gamelist::gamenight_id.eq(gamenight_id)) - .load::(c)?; - - linked_game_ids.iter().map(|l| { - known_games::table - .filter(known_games::id.eq(l.game_id)) - .first::(c) - }).collect() - }).await?) +pub async fn get_games_of_gamenight( + conn: &DbConn, + gamenight_id: Uuid, +) -> Result, DatabaseError> { + Ok(conn + .run::<_, Result, _>>(move |c| { + let linked_game_ids: Vec = gamenight_gamelist::table + .filter(gamenight_gamelist::gamenight_id.eq(gamenight_id)) + .load::(c)?; + + linked_game_ids + .iter() + .map(|l| { + known_games::table + .filter(known_games::id.eq(l.game_id)) + .first::(c) + }) + .collect() + }) + .await?) } pub async fn add_game(conn: &DbConn, game: Game) -> Result { @@ -161,17 +169,23 @@ pub async fn add_unknown_games(conn: &DbConn, games: &mut Vec) -> Result<( Ok(()) } -pub async fn load_participants(conn: &DbConn, gamenight_id: Uuid) -> Result, DatabaseError> { +pub async fn load_participants( + conn: &DbConn, + gamenight_id: Uuid, +) -> Result, DatabaseError> { Ok(conn .run::<_, Result, _>>(move |c| { let linked_participants = gamenight_participants::table .filter(gamenight_participants::gamenight_id.eq(gamenight_id)) .load::(c)?; - linked_participants.iter().map(|l| { - users::table - .filter(users::id.eq(l.user_id)) - .first::(c) - }).collect() + linked_participants + .iter() + .map(|l| { + users::table + .filter(users::id.eq(l.user_id)) + .first::(c) + }) + .collect() }) .await?) } From 9de8ffaa2d93c5eaa94afdb4503f47da3d46d4a3 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Sun, 29 May 2022 10:46:05 +0200 Subject: [PATCH 7/7] Some Cleaup. --- frontend/src/App.js | 3 --- frontend/src/components/Gamenight.jsx | 1 - frontend/src/components/Gamenights.jsx | 4 +++- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/App.js b/frontend/src/App.js index d2164e6..211c285 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -100,9 +100,6 @@ function App() { setUser(JSON.parse(localStorage.getItem(localStorageUserKey))); }, []); - - console.log(activeGamenight); - if(user === null) { return (
    diff --git a/frontend/src/components/Gamenight.jsx b/frontend/src/components/Gamenight.jsx index da56d99..2664163 100644 --- a/frontend/src/components/Gamenight.jsx +++ b/frontend/src/components/Gamenight.jsx @@ -1,7 +1,6 @@ import * as React from 'react'; function Gamenight(props) { - console.log(props.gamenight); let games = props.gamenight.game_list.map(g => ( diff --git a/frontend/src/components/Gamenights.jsx b/frontend/src/components/Gamenights.jsx index 0f843a2..bf54a28 100644 --- a/frontend/src/components/Gamenights.jsx +++ b/frontend/src/components/Gamenights.jsx @@ -36,7 +36,9 @@ function Gamenights(props) {
  • props.onSelectGamenight(g)}> {g.name} {(props.user.id === g.owner_id || props.user.role === "Admin") && - + }
  • )