Adds the ability to add games with suggestions from known games.

This commit is contained in:
Dennis Brentjes 2022-05-27 20:53:12 +02:00
parent cc26aed9a5
commit 1a6ead4760
18 changed files with 1240 additions and 168 deletions

42
backend/Cargo.lock generated
View File

@ -342,10 +342,12 @@ version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d"
dependencies = [ dependencies = [
"bitflags",
"byteorder", "byteorder",
"diesel_derives", "diesel_derives",
"libsqlite3-sys", "pq-sys",
"r2d2", "r2d2",
"uuid",
] ]
[[package]] [[package]]
@ -583,7 +585,6 @@ dependencies = [
"diesel-derive-enum", "diesel-derive-enum",
"diesel_migrations", "diesel_migrations",
"jsonwebtoken", "jsonwebtoken",
"libsqlite3-sys",
"local-ip-address", "local-ip-address",
"password-hash", "password-hash",
"rand_core", "rand_core",
@ -592,6 +593,7 @@ dependencies = [
"rocket_dyn_templates", "rocket_dyn_templates",
"rocket_sync_db_pools", "rocket_sync_db_pools",
"serde", "serde",
"uuid",
"validator", "validator",
] ]
@ -913,17 +915,6 @@ version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
[[package]]
name = "libsqlite3-sys"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e704a02bcaecd4a08b93a23f6be59d0bd79cd161e0963e9499165a0a35df7bd"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "local-ip-address" name = "local-ip-address"
version = "0.4.4" version = "0.4.4"
@ -1392,12 +1383,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]] [[package]]
name = "polyval" name = "polyval"
version = "0.5.3" version = "0.5.3"
@ -1416,6 +1401,15 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "pq-sys"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda"
dependencies = [
"vcpkg",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -2274,6 +2268,16 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "uuid"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [
"getrandom",
"serde",
]
[[package]] [[package]]
name = "validator" name = "validator"
version = "0.14.0" version = "0.14.0"

View File

@ -8,9 +8,8 @@ edition = "2018"
[dependencies] [dependencies]
rocket = { version = "0.5.0-rc.2", features = ["default", "json"] } rocket = { version = "0.5.0-rc.2", features = ["default", "json"] }
libsqlite3-sys = { version = ">=0.8.0, <0.19.0", features = ["bundled"] } rocket_sync_db_pools = { version = "0.1.0-rc.2", features = ["diesel_postgres_pool"] }
rocket_sync_db_pools = { version = "0.1.0-rc.2", features = ["diesel_sqlite_pool"] } diesel = {version = "1.4.8", features = ["uuidv07", "r2d2", "postgres"]}
diesel = { version = "1.4.8", features = ["sqlite"] }
diesel_migrations = "1.4.0" diesel_migrations = "1.4.0"
rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["handlebars"] } rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["handlebars"] }
chrono = "0.4.19" chrono = "0.4.19"
@ -18,8 +17,9 @@ serde = "1.0.136"
password-hash = "0.4" password-hash = "0.4"
argon2 = "0.4" argon2 = "0.4"
rand_core = { version = "0.6", features = ["std"] } rand_core = { version = "0.6", features = ["std"] }
diesel-derive-enum = { version = "1.1", features = ["sqlite"] } diesel-derive-enum = { version = "1.1", features = ["postgres"] }
jsonwebtoken = "8.1" jsonwebtoken = "8.1"
validator = { version = "0.14", features = ["derive"] } validator = { version = "0.14", features = ["derive"] }
rocket_cors = "0.6.0-alpha1" rocket_cors = "0.6.0-alpha1"
local-ip-address = "0.4" local-ip-address = "0.4"
uuid = { version = "0.8.2", features = ["v4", "serde"] }

View File

@ -1,12 +1,12 @@
-- Your SQL goes here -- Your SQL goes here
CREATE TABLE gamenight ( CREATE TABLE gamenight (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, id UUID NOT NULL PRIMARY KEY,
game text TEXT NOT NULL, name VARCHAR NOT NULL,
datetime TEXT NOT NULL datetime VARCHAR NOT NULL
); );
CREATE TABLE known_games ( CREATE TABLE known_games (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, id UUID NOT NULL PRIMARY KEY,
game TEXT UNIQUE NOT NULL name VARCHAR UNIQUE NOT NULL
); );

View File

@ -1,4 +1,4 @@
-- This file should undo anything in `up.sql` -- This file should undo anything in `up.sql`
drop table pwd; drop table pwd;
drop table user; drop table users;

View File

@ -1,19 +1,26 @@
CREATE TABLE user ( CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, id UUID NOT NULL PRIMARY KEY,
username TEXT UNIQUE NOT NULL, username VARCHAR UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL, email VARCHAR UNIQUE NOT NULL,
role TEXT NOT NULL role VARCHAR NOT NULL
); );
CREATE TABLE pwd ( CREATE TABLE pwd (
user_id INTEGER NOT NULL PRIMARY KEY, user_id UUID NOT NULL PRIMARY KEY,
password TEXT NOT NULL, password VARCHAR NOT NULL,
CONSTRAINT FK_UserId FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE CONSTRAINT FK_UserId FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
); );
--Initialize default admin user, with password "gamenight!" --Initialize default admin user, with password "gamenight!"
INSERT INTO user (id, username, role) CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
values(-1, 'admin', 'admin'); DO $$
DECLARE
admin_uuid uuid = uuid_generate_v4();
BEGIN
INSERT INTO users (id, username, email, role)
values(admin_uuid, 'admin', '', 'admin');
insert INTO pwd (user_id, password)
values(admin_uuid, '$argon2id$v=19$m=4096,t=3,p=1$zEdUjCAnZqd8DziYWzlFHw$YBLQhKvYIZBY43B8zM6hyBvLKuqTeh0EM5pKOfbWQSI');
END $$;
insert INTO pwd (id, pwd)
values(-1, '$argon2id$v=19$m=4096,t=3,p=1$zEdUjCAnZqd8DziYWzlFHw$YBLQhKvYIZBY43B8zM6hyBvLKuqTeh0EM5pKOfbWQSI');

View File

@ -1,19 +1,19 @@
ALTER TABLE gamenight RENAME TO _gamenight_old; ALTER TABLE gamenight RENAME TO _gamenight_old;
CREATE TABLE gamenight ( CREATE TABLE gamenight (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, id UUID NOT NULL PRIMARY KEY,
game text TEXT NOT NULL, name VARCHAR NOT NULL,
datetime TEXT NOT NULL, datetime VARCHAR NOT NULL,
owner_id INTEGER NOT NULL, owner_id UUID NOT NULL,
CONSTRAINT FK_OwnerId FOREIGN KEY (owner_id) REFERENCES user(id) ON DELETE CASCADE CONSTRAINT FK_OwnerId FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE
); );
PRAGMA foreign_keys=off; SET session_replication_role = 'replica';
INSERT INTO gamenight (id, game, datetime, owner_id) INSERT INTO gamenight (id, name, datetime, owner_id)
select id, game, datetime, -1 select id, name, datetime, '00000000-0000-0000-0000-000000000000'
FROM _gamenight_old; FROM _gamenight_old;
drop table _gamenight_old; drop table _gamenight_old;
PRAGMA foreign_keys=on; SET session_replication_role = 'origin';

View File

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
drop table gamenight_gamelist;

View File

@ -0,0 +1,9 @@
-- Your SQL goes here
create table gamenight_gamelist (
gamenight_id UUID NOT NULL,
game_id UUID NOT NULL,
CONSTRAINT FK_gamenight_id FOREIGN KEY (gamenight_id) REFERENCES gamenight(id) ON DELETE CASCADE,
CONSTRAINT FK_game_id FOREIGN KEY (game_id) REFERENCES known_games(id) ON DELETE CASCADE,
PRIMARY KEY(gamenight_id, game_id)
);

View File

@ -1,3 +1,4 @@
use uuid::Uuid;
use crate::schema; use crate::schema;
use crate::schema::DbConn; use crate::schema::DbConn;
use crate::AppConfig; use crate::AppConfig;
@ -40,6 +41,8 @@ struct ApiResponse {
user: Option<UserWithToken>, user: Option<UserWithToken>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
gamenights: Option<Vec<schema::GameNight>>, gamenights: Option<Vec<schema::GameNight>>,
#[serde(skip_serializing_if = "Option::is_none")]
games: Option<Vec<schema::Game>>,
} }
impl ApiResponse { impl ApiResponse {
@ -51,6 +54,7 @@ impl ApiResponse {
message: None, message: None,
user: None, user: None,
gamenights: None, gamenights: None,
games: None,
}; };
fn error(message: String) -> Self { fn error(message: String) -> Self {
@ -59,6 +63,7 @@ impl ApiResponse {
message: Some(Cow::Owned(message)), message: Some(Cow::Owned(message)),
user: None, user: None,
gamenights: None, gamenights: None,
games: None,
} }
} }
@ -71,6 +76,7 @@ impl ApiResponse {
jwt: jwt jwt: jwt
}), }),
gamenights: None, gamenights: None,
games: None,
} }
} }
@ -80,6 +86,17 @@ impl ApiResponse {
message: None, message: None,
user: None, user: None,
gamenights: Some(gamenights), gamenights: Some(gamenights),
games: None,
}
}
fn games_response(games: Vec<schema::Game>) -> Self {
Self {
result: Self::SUCCES_RESULT,
message: None,
user: None,
gamenights: None,
games: Some(games),
} }
} }
} }
@ -143,27 +160,55 @@ pub async fn gamenights_unauthorized() -> ApiResponseVariant {
ApiResponseVariant::Status(Status::Unauthorized) ApiResponseVariant::Status(Status::Unauthorized)
} }
#[post("/gamenight", format = "application/json", data = "<gamenight_json>")] #[derive(Debug, Serialize, Deserialize, Clone)]
pub async fn gamenight_post_json( pub struct GameNightInput {
conn: DbConn, pub name: String,
user: schema::User, pub datetime: String,
gamenight_json: Json<schema::GameNightNoId>, pub owner_id: Option<Uuid>,
) -> ApiResponseVariant { pub game_list: Vec<schema::Game>,
let mut gamenight = gamenight_json.into_inner(); }
gamenight.owner_id = Some(user.id);
match schema::insert_gamenight(conn, gamenight).await { impl Into<schema::GameNight> for GameNightInput {
Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)),
Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), fn into(self) -> schema::GameNight {
schema::GameNight {
id: Uuid::new_v4(),
name: self.name,
datetime: self.datetime,
owner_id: self.owner_id.unwrap()
}
} }
} }
#[post("/gamenight", rank = 2)] #[post("/gamenights", format = "application/json", data = "<gamenight_json>")]
pub async fn gamenight_post_json_unauthorized() -> ApiResponseVariant { pub async fn gamenights_post_json(
conn: DbConn,
user: schema::User,
gamenight_json: Json<GameNightInput>,
) -> ApiResponseVariant {
let mut gamenight = gamenight_json.into_inner();
gamenight.owner_id = Some(user.id);
let mut mutable_game_list = gamenight.game_list.clone();
match schema::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 {
Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)),
Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string())))
}
}
#[post("/gamenights", rank = 2)]
pub async fn gamenights_post_json_unauthorized() -> ApiResponseVariant {
ApiResponseVariant::Status(Status::Unauthorized) ApiResponseVariant::Status(Status::Unauthorized)
} }
#[delete("/gamenight", format = "application/json", data = "<delete_gamenight_json>")] #[delete("/gamenights", format = "application/json", data = "<delete_gamenight_json>")]
pub async fn gamenight_delete_json( pub async fn gamenights_delete_json(
conn: DbConn, conn: DbConn,
user: schema::User, user: schema::User,
delete_gamenight_json: Json<schema::DeleteGameNight> delete_gamenight_json: Json<schema::DeleteGameNight>
@ -190,8 +235,8 @@ pub async fn gamenight_delete_json(
} }
#[delete("/gamenight", rank = 2)] #[delete("/gamenights", rank = 2)]
pub async fn gamenight_delete_json_unauthorized() -> ApiResponseVariant { pub async fn gamenights_delete_json_unauthorized() -> ApiResponseVariant {
ApiResponseVariant::Status(Status::Unauthorized) ApiResponseVariant::Status(Status::Unauthorized)
} }
@ -221,7 +266,7 @@ pub async fn register_post_json(
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct Claims { struct Claims {
exp: i64, exp: i64,
uid: i32, uid: Uuid,
role: schema::Role, role: schema::Role,
} }
@ -261,3 +306,17 @@ 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 {
Ok(games) => ApiResponseVariant::Value(json!(ApiResponse::games_response(games))),
Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string())))
}
}
#[get("/games", rank = 2)]
pub async fn games_unauthorized() -> ApiResponseVariant {
ApiResponseVariant::Status(Status::Unauthorized)
}

View File

@ -58,12 +58,14 @@ async fn rocket() -> _ {
routes![ routes![
api::gamenights, api::gamenights,
api::gamenights_unauthorized, api::gamenights_unauthorized,
api::gamenight_post_json, api::gamenights_post_json,
api::gamenight_post_json_unauthorized, api::gamenights_post_json_unauthorized,
api::register_post_json, api::register_post_json,
api::login_post_json, api::login_post_json,
api::gamenight_delete_json, api::gamenights_delete_json,
api::gamenight_delete_json_unauthorized api::gamenights_delete_json_unauthorized,
api::games,
api::games_unauthorized,
], ],
); );

View File

@ -1,4 +1,4 @@
use crate::diesel::BoolExpressionMethods; use uuid::Uuid;
use crate::diesel::Connection; use crate::diesel::Connection;
use crate::diesel::ExpressionMethods; use crate::diesel::ExpressionMethods;
use crate::diesel::QueryDsl; use crate::diesel::QueryDsl;
@ -9,7 +9,6 @@ use argon2::{
password_hash::{rand_core::OsRng, PasswordHasher}, password_hash::{rand_core::OsRng, PasswordHasher},
Argon2, Argon2,
}; };
use diesel::dsl::count;
use diesel::RunQueryDsl; use diesel::RunQueryDsl;
use diesel_derive_enum::DbEnum; use diesel_derive_enum::DbEnum;
use rocket::{Build, Rocket}; use rocket::{Build, Rocket};
@ -19,10 +18,10 @@ use std::ops::Deref;
use validator::{Validate, ValidationError}; use validator::{Validate, ValidationError};
#[database("gamenight_database")] #[database("gamenight_database")]
pub struct DbConn(diesel::SqliteConnection); pub struct DbConn(diesel::PgConnection);
impl Deref for DbConn { impl Deref for DbConn {
type Target = rocket_sync_db_pools::Connection<DbConn, diesel::SqliteConnection>; type Target = rocket_sync_db_pools::Connection<DbConn, diesel::PgConnection>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.0 &self.0
@ -31,36 +30,40 @@ impl Deref for DbConn {
table! { table! {
gamenight (id) { gamenight (id) {
id -> Integer, id -> diesel::sql_types::Uuid,
game -> Text, name -> VarChar,
datetime -> Text, datetime -> VarChar,
owner_id -> Integer, owner_id -> Uuid,
} }
} }
table! { table! {
known_games (game) { known_games (id) {
id -> Integer, id -> diesel::sql_types::Uuid,
game -> Text, name -> VarChar,
} }
} }
table! { table! {
use diesel::sql_types::Integer; users(id) {
use diesel::sql_types::Text; id -> diesel::sql_types::Uuid,
use super::RoleMapping; username -> VarChar,
user(id) { email -> VarChar,
id -> Integer, role -> crate::schema::RoleMapping,
username -> Text,
email -> Text,
role -> RoleMapping,
} }
} }
table! { table! {
pwd(user_id) { pwd(user_id) {
user_id -> Integer, user_id -> diesel::sql_types::Uuid,
password -> Text, password -> VarChar,
}
}
table! {
gamenight_gamelist(gamenight_id, game_id) {
gamenight_id -> diesel::sql_types::Uuid,
game_id -> diesel::sql_types::Uuid,
} }
} }
@ -98,21 +101,31 @@ pub async fn get_all_gamenights(conn: DbConn) -> Result<Vec<GameNight>, Database
).await?) ).await?)
} }
pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNightNoId) -> Result<usize, DatabaseError> { pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNight, game_list: Vec<Game>) -> Result<usize, DatabaseError> {
Ok(conn.run(|c| Ok(conn.run(move |c|
diesel::insert_into(gamenight::table) c.transaction(|| {
.values(new_gamenight) diesel::insert_into(gamenight::table)
.execute(c) .values(&new_gamenight)
.execute(c)?;
let entries: Vec<GamenightGameListEntry> = 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?) ).await?)
} }
pub async fn get_gamenight(conn: &DbConn, game_id: i32) -> Result<GameNight, DatabaseError> { pub async fn get_gamenight(conn: &DbConn, game_id: Uuid) -> Result<GameNight, DatabaseError> {
Ok(conn.run(move |c| Ok(conn.run(move |c|
gamenight::table.find(game_id).first(c) gamenight::table.find(game_id).first(c)
).await?) ).await?)
} }
pub async fn delete_gamenight(conn: &DbConn, game_id: i32) -> Result<usize, DatabaseError> { pub async fn delete_gamenight(conn: &DbConn, game_id: Uuid) -> Result<usize, DatabaseError> {
Ok(conn.run(move |c| Ok(conn.run(move |c|
diesel::delete( diesel::delete(
gamenight::table.filter( gamenight::table.filter(
@ -131,25 +144,22 @@ pub async fn insert_user(conn: DbConn, new_user: Register) -> Result<usize, Data
Ok(conn.run(move |c| { Ok(conn.run(move |c| {
c.transaction(|| { c.transaction(|| {
diesel::insert_into(user::table) let id = Uuid::new_v4();
.values((
user::username.eq(&new_user.username), diesel::insert_into(users::table)
user::email.eq(&new_user.email), .values(User {
user::role.eq(Role::User), id: id.clone(),
)) username: new_user.username,
email: new_user.email,
role: Role::User
})
.execute(c)?; .execute(c)?;
let id: i32 = user::table
.filter(
user::username
.eq(&new_user.username)
.and(user::email.eq(&new_user.email)),
)
.select(user::id)
.first(c)?;
diesel::insert_into(pwd::table) diesel::insert_into(pwd::table)
.values((pwd::user_id.eq(id), pwd::password.eq(&password_hash))) .values(Pwd {
user_id: id,
password: password_hash
})
.execute(c) .execute(c)
}) })
}) })
@ -158,10 +168,10 @@ pub async fn insert_user(conn: DbConn, new_user: Register) -> Result<usize, Data
pub async fn login(conn: DbConn, login: Login) -> Result<LoginResult, DatabaseError> { pub async fn login(conn: DbConn, login: Login) -> Result<LoginResult, DatabaseError> {
conn.run(move |c| -> Result<LoginResult, DatabaseError> { conn.run(move |c| -> Result<LoginResult, DatabaseError> {
let id: i32 = user::table let id: Uuid = users::table
.filter(user::username.eq(&login.username)) .filter(users::username.eq(&login.username))
.or_filter(user::email.eq(&login.username)) .or_filter(users::email.eq(&login.username))
.select(user::id) .select(users::id)
.first(c)?; .first(c)?;
let pwd: String = pwd::table let pwd: String = pwd::table
@ -175,7 +185,7 @@ pub async fn login(conn: DbConn, login: Login) -> Result<LoginResult, DatabaseEr
.verify_password(&login.password.as_bytes(), &parsed_hash) .verify_password(&login.password.as_bytes(), &parsed_hash)
.is_ok() .is_ok()
{ {
let user: User = user::table.find(id).first(c)?; let user: User = users::table.find(id).first(c)?;
Ok(LoginResult { Ok(LoginResult {
result: true, result: true,
@ -191,19 +201,46 @@ pub async fn login(conn: DbConn, login: Login) -> Result<LoginResult, DatabaseEr
.await .await
} }
pub async fn get_user(conn: DbConn, id: i32) -> Result<User, DatabaseError> { pub async fn get_user(conn: DbConn, id: Uuid) -> Result<User, DatabaseError> {
Ok(conn.run(move |c| user::table.filter(user::id.eq(id)).first(c)) Ok(conn.run(move |c| users::table.filter(users::id.eq(id)).first(c))
.await?) .await?)
} }
pub async fn get_all_known_games(conn: &DbConn) -> Result<Vec<Game>, DatabaseError> {
Ok(conn.run(|c|
known_games::table.load::<Game>(c)
).await?)
}
pub async fn add_game(conn: &DbConn, game: Game) -> Result<usize, DatabaseError> {
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<Game>) -> 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( pub fn unique_username(
username: &String, username: &String,
conn: &diesel::SqliteConnection, conn: &diesel::PgConnection,
) -> Result<(), ValidationError> { ) -> Result<(), ValidationError> {
match user::table match users::table
.select(count(user::username)) .count()
.filter(user::username.eq(username)) .filter(users::username.eq(username))
.execute(conn) .get_result(conn)
{ {
Ok(0) => Ok(()), Ok(0) => Ok(()),
Ok(_) => Err(ValidationError::new("User already exists")), Ok(_) => Err(ValidationError::new("User already exists")),
@ -213,12 +250,12 @@ pub fn unique_username(
pub fn unique_email( pub fn unique_email(
email: &String, email: &String,
conn: &diesel::SqliteConnection, conn: &diesel::PgConnection,
) -> Result<(), ValidationError> { ) -> Result<(), ValidationError> {
match user::table match users::table
.select(count(user::email)) .count()
.filter(user::email.eq(email)) .filter(users::email.eq(email))
.execute(conn) .get_result(conn)
{ {
Ok(0) => Ok(()), Ok(0) => Ok(()),
Ok(_) => Err(ValidationError::new("email already exists")), Ok(_) => Err(ValidationError::new("email already exists")),
@ -249,57 +286,60 @@ pub enum Role {
} }
#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] #[derive(Serialize, Deserialize, Debug, Insertable, Queryable)]
#[table_name = "user"] #[table_name = "users"]
pub struct User { pub struct User {
pub id: i32, pub id: Uuid,
pub username: String, pub username: String,
pub email: String, pub email: String,
pub role: Role, pub role: Role,
} }
#[derive(Serialize, Deserialize, Debug, FromForm, Insertable)] #[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"] #[table_name = "known_games"]
pub struct GameNoId {
pub game: String,
}
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)]
pub struct Game { pub struct Game {
pub id: i32, pub id: Uuid,
pub game: String, pub name: String,
} }
#[derive(Serialize, Deserialize, Debug, Insertable)] #[derive(Serialize, Deserialize, Debug, Queryable, Insertable)]
#[table_name = "gamenight"] #[table_name = "gamenight"]
pub struct GameNightNoId {
pub game: String,
pub datetime: String,
pub owner_id: Option<i32>
}
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)]
pub struct GameNight { pub struct GameNight {
pub id: i32, pub id: Uuid,
pub game: String, pub name: String,
pub datetime: String, pub datetime: String,
pub owner_id: i32, pub owner_id: Uuid,
} }
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)] #[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 struct DeleteGameNight {
pub game_id: i32, pub game_id: Uuid,
} }
#[derive(Serialize, Deserialize, Debug, Validate, Clone)] #[derive(Serialize, Deserialize, Debug, Validate, Clone)]
pub struct Register { pub struct Register {
#[validate( #[validate(
length(min = 1), length(min = 1),
custom(function = "unique_username", arg = "&'v_a diesel::SqliteConnection") custom(function = "unique_username", arg = "&'v_a diesel::PgConnection")
)] )]
pub username: String, pub username: String,
#[validate( #[validate(
email, email,
custom(function = "unique_email", arg = "&'v_a diesel::SqliteConnection") custom(function = "unique_email", arg = "&'v_a diesel::PgConnection")
)] )]
pub email: String, pub email: String,
#[validate(length(min = 10), must_match = "password_repeat")] #[validate(length(min = 10), must_match = "password_repeat")]

23
docker-compose.yml Normal file
View File

@ -0,0 +1,23 @@
version: '3.8'
services:
db:
container_name: pg_container
image: postgres
restart: always
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: root
POSTGRES_DB: gamenight
ports:
- "5432:5432"
pgadmin:
container_name: pgadmin4_container
image: dpage/pgadmin4
restart: always
environment:
PGADMIN_DEFAULT_EMAIL: admin@admin.com
PGADMIN_DEFAULT_PASSWORD: root
ports:
- "5050:80"

View File

@ -8,6 +8,11 @@
"name": "frontend", "name": "frontend",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@material-ui/icons": "^4.11.3",
"@mui/icons-material": "^5.8.0",
"@mui/material": "^5.8.0",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.0.1", "@testing-library/react": "^13.0.1",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
@ -2171,6 +2176,174 @@
"postcss-selector-parser": "^6.0.10" "postcss-selector-parser": "^6.0.10"
} }
}, },
"node_modules/@emotion/babel-plugin": {
"version": "11.9.2",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz",
"integrity": "sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==",
"dependencies": {
"@babel/helper-module-imports": "^7.12.13",
"@babel/plugin-syntax-jsx": "^7.12.13",
"@babel/runtime": "^7.13.10",
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.5",
"@emotion/serialize": "^1.0.2",
"babel-plugin-macros": "^2.6.1",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0",
"find-root": "^1.1.0",
"source-map": "^0.5.7",
"stylis": "4.0.13"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@emotion/babel-plugin/node_modules/babel-plugin-macros": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
"integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
"dependencies": {
"@babel/runtime": "^7.7.2",
"cosmiconfig": "^6.0.0",
"resolve": "^1.12.0"
}
},
"node_modules/@emotion/babel-plugin/node_modules/cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"dependencies": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@emotion/babel-plugin/node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/@emotion/cache": {
"version": "11.7.1",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
"integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
"dependencies": {
"@emotion/memoize": "^0.7.4",
"@emotion/sheet": "^1.1.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"stylis": "4.0.13"
}
},
"node_modules/@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
"node_modules/@emotion/is-prop-valid": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz",
"integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==",
"dependencies": {
"@emotion/memoize": "^0.7.4"
}
},
"node_modules/@emotion/memoize": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
},
"node_modules/@emotion/react": {
"version": "11.9.0",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.0.tgz",
"integrity": "sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@emotion/babel-plugin": "^11.7.1",
"@emotion/cache": "^11.7.1",
"@emotion/serialize": "^1.0.3",
"@emotion/utils": "^1.1.0",
"@emotion/weak-memoize": "^0.2.5",
"hoist-non-react-statics": "^3.3.1"
},
"peerDependencies": {
"@babel/core": "^7.0.0",
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@emotion/serialize": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz",
"integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==",
"dependencies": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
"@emotion/unitless": "^0.7.5",
"@emotion/utils": "^1.0.0",
"csstype": "^3.0.2"
}
},
"node_modules/@emotion/sheet": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz",
"integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
},
"node_modules/@emotion/styled": {
"version": "11.8.1",
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.8.1.tgz",
"integrity": "sha512-OghEVAYBZMpEquHZwuelXcRjRJQOVayvbmNR0zr174NHdmMgrNkLC6TljKC5h9lZLkN5WGrdUcrKlOJ4phhoTQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@emotion/babel-plugin": "^11.7.1",
"@emotion/is-prop-valid": "^1.1.2",
"@emotion/serialize": "^1.0.2",
"@emotion/utils": "^1.1.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0",
"@emotion/react": "^11.0.0-rc.0",
"react": ">=16.8.0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@emotion/unitless": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
},
"node_modules/@emotion/utils": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz",
"integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ=="
},
"node_modules/@emotion/weak-memoize": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
"node_modules/@eslint/eslintrc": { "node_modules/@eslint/eslintrc": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz",
@ -2732,6 +2905,259 @@
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
}, },
"node_modules/@material-ui/icons": {
"version": "4.11.3",
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz",
"integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==",
"dependencies": {
"@babel/runtime": "^7.4.4"
},
"engines": {
"node": ">=8.0.0"
},
"peerDependencies": {
"@material-ui/core": "^4.0.0",
"@types/react": "^16.8.6 || ^17.0.0",
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/base": {
"version": "5.0.0-alpha.81",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.81.tgz",
"integrity": "sha512-KJP+RdKBLSbhiAliy1b5xFuoAezawupfIHc/MRtEZdqAmUW0+UFNDXIUDlBKR9zLCjgjQ7eVJsSe0TwAgd8OMQ==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@emotion/is-prop-valid": "^1.1.2",
"@mui/types": "^7.1.3",
"@mui/utils": "^5.8.0",
"@popperjs/core": "^2.11.5",
"clsx": "^1.1.1",
"prop-types": "^15.8.1",
"react-is": "^17.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/icons-material": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.8.0.tgz",
"integrity": "sha512-ScwLxa0q5VYV70Jfc60V/9VD0b9SvIeZ0Jddx2Dt2pBUFFO9vKdrbt9LYiT+4p21Au5NdYIb2XSHj46CLN1v3g==",
"dependencies": {
"@babel/runtime": "^7.17.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"@mui/material": "^5.0.0",
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.8.0.tgz",
"integrity": "sha512-yvt3sUmUZ1i8SPadRYBCThcB57lBZsvyhC7ufVpRxA3AD39O+WXtXAapEfpDdDkJnnKb5MCimDMwBYgWLmY89Q==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@mui/base": "5.0.0-alpha.81",
"@mui/system": "^5.8.0",
"@mui/types": "^7.1.3",
"@mui/utils": "^5.8.0",
"@types/react-transition-group": "^4.4.4",
"clsx": "^1.1.1",
"csstype": "^3.0.11",
"hoist-non-react-statics": "^3.3.2",
"prop-types": "^15.8.1",
"react-is": "^17.0.2",
"react-transition-group": "^4.4.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/private-theming": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.8.0.tgz",
"integrity": "sha512-MjRAneTmCKLR9u2S4jtjLUe6gpHxlbb4g2bqpDJ2PdwlvwsWIUzbc/gVB4dvccljXeWxr5G2M/Co2blXisvFIw==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@mui/utils": "^5.8.0",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/styled-engine": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.8.0.tgz",
"integrity": "sha512-Q3spibB8/EgeMYHc+/o3RRTnAYkSl7ROCLhXJ830W8HZ2/iDiyYp16UcxKPurkXvLhUaILyofPVrP3Su2uKsAw==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@emotion/cache": "^11.7.1",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
}
}
},
"node_modules/@mui/system": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.8.0.tgz",
"integrity": "sha512-1tEj2S59RjlZ/6JMJMUktQDbV2ev7hyGXqO7dRRUQ7nOJi9qHmCFP0uXj3YS6LbM6hVasgYXJg8GBjbEtfTJvg==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@mui/private-theming": "^5.8.0",
"@mui/styled-engine": "^5.8.0",
"@mui/types": "^7.1.3",
"@mui/utils": "^5.8.0",
"clsx": "^1.1.1",
"csstype": "^3.0.11",
"prop-types": "^15.8.1"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0"
},
"peerDependenciesMeta": {
"@emotion/react": {
"optional": true
},
"@emotion/styled": {
"optional": true
},
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/types": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.3.tgz",
"integrity": "sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA==",
"peerDependencies": {
"@types/react": "*"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/utils": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.0.tgz",
"integrity": "sha512-7LgUtCvz78676iC0wpTH7HizMdCrTphhBmRWimIMFrp5Ph6JbDFVuKS1CwYnWWxRyYKL0QzXrDL0lptAU90EXg==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"@types/prop-types": "^15.7.5",
"@types/react-is": "^16.7.1 || ^17.0.0",
"prop-types": "^15.8.1",
"react-is": "^17.0.2"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0"
}
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -2821,6 +3247,15 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/@popperjs/core": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/plugin-babel": { "node_modules/@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@ -3501,6 +3936,22 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/react-is": {
"version": "17.0.3",
"resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz",
"integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/react-transition-group": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
"integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/resolve": { "node_modules/@types/resolve": {
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@ -5240,6 +5691,14 @@
"mimic-response": "^1.0.0" "mimic-response": "^1.0.0"
} }
}, },
"node_modules/clsx": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==",
"engines": {
"node": ">=6"
}
},
"node_modules/co": { "node_modules/co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -6275,6 +6734,15 @@
"utila": "~0.4" "utila": "~0.4"
} }
}, },
"node_modules/dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"dependencies": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"node_modules/dom-serializer": { "node_modules/dom-serializer": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
@ -7594,6 +8062,11 @@
"url": "https://github.com/avajs/find-cache-dir?sponsor=1" "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
} }
}, },
"node_modules/find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
},
"node_modules/find-up": { "node_modules/find-up": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@ -8210,6 +8683,19 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/hoist-non-react-statics/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/hoopy": { "node_modules/hoopy": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@ -13185,6 +13671,21 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/react-transition-group": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
"dependencies": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
},
"peerDependencies": {
"react": ">=16.6.0",
"react-dom": ">=16.6.0"
}
},
"node_modules/readable-stream": { "node_modules/readable-stream": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@ -14339,6 +14840,11 @@
"postcss": "^8.2.15" "postcss": "^8.2.15"
} }
}, },
"node_modules/stylis": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
"integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
},
"node_modules/supports-color": { "node_modules/supports-color": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@ -17497,6 +18003,142 @@
"integrity": "sha512-RkYG5KiGNX0fJ5YoI0f4Wfq2Yo74D25Hru4fxTOioYdQvHBxcrrtTTyT5Ozzh2ejcNrhFy7IEts2WyEY7yi5yw==", "integrity": "sha512-RkYG5KiGNX0fJ5YoI0f4Wfq2Yo74D25Hru4fxTOioYdQvHBxcrrtTTyT5Ozzh2ejcNrhFy7IEts2WyEY7yi5yw==",
"requires": {} "requires": {}
}, },
"@emotion/babel-plugin": {
"version": "11.9.2",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz",
"integrity": "sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==",
"requires": {
"@babel/helper-module-imports": "^7.12.13",
"@babel/plugin-syntax-jsx": "^7.12.13",
"@babel/runtime": "^7.13.10",
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.5",
"@emotion/serialize": "^1.0.2",
"babel-plugin-macros": "^2.6.1",
"convert-source-map": "^1.5.0",
"escape-string-regexp": "^4.0.0",
"find-root": "^1.1.0",
"source-map": "^0.5.7",
"stylis": "4.0.13"
},
"dependencies": {
"babel-plugin-macros": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz",
"integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==",
"requires": {
"@babel/runtime": "^7.7.2",
"cosmiconfig": "^6.0.0",
"resolve": "^1.12.0"
}
},
"cosmiconfig": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz",
"integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==",
"requires": {
"@types/parse-json": "^4.0.0",
"import-fresh": "^3.1.0",
"parse-json": "^5.0.0",
"path-type": "^4.0.0",
"yaml": "^1.7.2"
}
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
}
}
},
"@emotion/cache": {
"version": "11.7.1",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz",
"integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==",
"requires": {
"@emotion/memoize": "^0.7.4",
"@emotion/sheet": "^1.1.0",
"@emotion/utils": "^1.0.0",
"@emotion/weak-memoize": "^0.2.5",
"stylis": "4.0.13"
}
},
"@emotion/hash": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
"integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow=="
},
"@emotion/is-prop-valid": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz",
"integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==",
"requires": {
"@emotion/memoize": "^0.7.4"
}
},
"@emotion/memoize": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz",
"integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ=="
},
"@emotion/react": {
"version": "11.9.0",
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.0.tgz",
"integrity": "sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@emotion/babel-plugin": "^11.7.1",
"@emotion/cache": "^11.7.1",
"@emotion/serialize": "^1.0.3",
"@emotion/utils": "^1.1.0",
"@emotion/weak-memoize": "^0.2.5",
"hoist-non-react-statics": "^3.3.1"
}
},
"@emotion/serialize": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz",
"integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==",
"requires": {
"@emotion/hash": "^0.8.0",
"@emotion/memoize": "^0.7.4",
"@emotion/unitless": "^0.7.5",
"@emotion/utils": "^1.0.0",
"csstype": "^3.0.2"
}
},
"@emotion/sheet": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz",
"integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g=="
},
"@emotion/styled": {
"version": "11.8.1",
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.8.1.tgz",
"integrity": "sha512-OghEVAYBZMpEquHZwuelXcRjRJQOVayvbmNR0zr174NHdmMgrNkLC6TljKC5h9lZLkN5WGrdUcrKlOJ4phhoTQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@emotion/babel-plugin": "^11.7.1",
"@emotion/is-prop-valid": "^1.1.2",
"@emotion/serialize": "^1.0.2",
"@emotion/utils": "^1.1.0"
}
},
"@emotion/unitless": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
"integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg=="
},
"@emotion/utils": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz",
"integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ=="
},
"@emotion/weak-memoize": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
"@eslint/eslintrc": { "@eslint/eslintrc": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz",
@ -17922,6 +18564,109 @@
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz",
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
}, },
"@material-ui/icons": {
"version": "4.11.3",
"resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz",
"integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==",
"requires": {
"@babel/runtime": "^7.4.4"
}
},
"@mui/base": {
"version": "5.0.0-alpha.81",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.81.tgz",
"integrity": "sha512-KJP+RdKBLSbhiAliy1b5xFuoAezawupfIHc/MRtEZdqAmUW0+UFNDXIUDlBKR9zLCjgjQ7eVJsSe0TwAgd8OMQ==",
"requires": {
"@babel/runtime": "^7.17.2",
"@emotion/is-prop-valid": "^1.1.2",
"@mui/types": "^7.1.3",
"@mui/utils": "^5.8.0",
"@popperjs/core": "^2.11.5",
"clsx": "^1.1.1",
"prop-types": "^15.8.1",
"react-is": "^17.0.2"
}
},
"@mui/icons-material": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.8.0.tgz",
"integrity": "sha512-ScwLxa0q5VYV70Jfc60V/9VD0b9SvIeZ0Jddx2Dt2pBUFFO9vKdrbt9LYiT+4p21Au5NdYIb2XSHj46CLN1v3g==",
"requires": {
"@babel/runtime": "^7.17.2"
}
},
"@mui/material": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.8.0.tgz",
"integrity": "sha512-yvt3sUmUZ1i8SPadRYBCThcB57lBZsvyhC7ufVpRxA3AD39O+WXtXAapEfpDdDkJnnKb5MCimDMwBYgWLmY89Q==",
"requires": {
"@babel/runtime": "^7.17.2",
"@mui/base": "5.0.0-alpha.81",
"@mui/system": "^5.8.0",
"@mui/types": "^7.1.3",
"@mui/utils": "^5.8.0",
"@types/react-transition-group": "^4.4.4",
"clsx": "^1.1.1",
"csstype": "^3.0.11",
"hoist-non-react-statics": "^3.3.2",
"prop-types": "^15.8.1",
"react-is": "^17.0.2",
"react-transition-group": "^4.4.2"
}
},
"@mui/private-theming": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.8.0.tgz",
"integrity": "sha512-MjRAneTmCKLR9u2S4jtjLUe6gpHxlbb4g2bqpDJ2PdwlvwsWIUzbc/gVB4dvccljXeWxr5G2M/Co2blXisvFIw==",
"requires": {
"@babel/runtime": "^7.17.2",
"@mui/utils": "^5.8.0",
"prop-types": "^15.8.1"
}
},
"@mui/styled-engine": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.8.0.tgz",
"integrity": "sha512-Q3spibB8/EgeMYHc+/o3RRTnAYkSl7ROCLhXJ830W8HZ2/iDiyYp16UcxKPurkXvLhUaILyofPVrP3Su2uKsAw==",
"requires": {
"@babel/runtime": "^7.17.2",
"@emotion/cache": "^11.7.1",
"prop-types": "^15.8.1"
}
},
"@mui/system": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.8.0.tgz",
"integrity": "sha512-1tEj2S59RjlZ/6JMJMUktQDbV2ev7hyGXqO7dRRUQ7nOJi9qHmCFP0uXj3YS6LbM6hVasgYXJg8GBjbEtfTJvg==",
"requires": {
"@babel/runtime": "^7.17.2",
"@mui/private-theming": "^5.8.0",
"@mui/styled-engine": "^5.8.0",
"@mui/types": "^7.1.3",
"@mui/utils": "^5.8.0",
"clsx": "^1.1.1",
"csstype": "^3.0.11",
"prop-types": "^15.8.1"
}
},
"@mui/types": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.3.tgz",
"integrity": "sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA==",
"requires": {}
},
"@mui/utils": {
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.0.tgz",
"integrity": "sha512-7LgUtCvz78676iC0wpTH7HizMdCrTphhBmRWimIMFrp5Ph6JbDFVuKS1CwYnWWxRyYKL0QzXrDL0lptAU90EXg==",
"requires": {
"@babel/runtime": "^7.17.2",
"@types/prop-types": "^15.7.5",
"@types/react-is": "^16.7.1 || ^17.0.0",
"prop-types": "^15.8.1",
"react-is": "^17.0.2"
}
},
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -17968,6 +18713,11 @@
} }
} }
}, },
"@popperjs/core": {
"version": "2.11.5",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw=="
},
"@rollup/plugin-babel": { "@rollup/plugin-babel": {
"version": "5.3.1", "version": "5.3.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@ -18486,6 +19236,22 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"@types/react-is": {
"version": "17.0.3",
"resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz",
"integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==",
"requires": {
"@types/react": "*"
}
},
"@types/react-transition-group": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz",
"integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==",
"requires": {
"@types/react": "*"
}
},
"@types/resolve": { "@types/resolve": {
"version": "1.17.1", "version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@ -19765,6 +20531,11 @@
"mimic-response": "^1.0.0" "mimic-response": "^1.0.0"
} }
}, },
"clsx": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
},
"co": { "co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -20521,6 +21292,15 @@
"utila": "~0.4" "utila": "~0.4"
} }
}, },
"dom-helpers": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
"requires": {
"@babel/runtime": "^7.8.7",
"csstype": "^3.0.2"
}
},
"dom-serializer": { "dom-serializer": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
@ -21498,6 +22278,11 @@
"pkg-dir": "^4.1.0" "pkg-dir": "^4.1.0"
} }
}, },
"find-root": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
"integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="
},
"find-up": { "find-up": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@ -21916,6 +22701,21 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
}, },
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"requires": {
"react-is": "^16.7.0"
},
"dependencies": {
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
}
}
},
"hoopy": { "hoopy": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@ -25413,6 +26213,17 @@
} }
} }
}, },
"react-transition-group": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz",
"integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==",
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
}
},
"readable-stream": { "readable-stream": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
@ -26269,6 +27080,11 @@
"postcss-selector-parser": "^6.0.4" "postcss-selector-parser": "^6.0.4"
} }
}, },
"stylis": {
"version": "4.0.13",
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz",
"integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag=="
},
"supports-color": { "supports-color": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",

View File

@ -3,6 +3,11 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.9.0",
"@emotion/styled": "^11.8.1",
"@material-ui/icons": "^4.11.3",
"@mui/icons-material": "^5.8.0",
"@mui/material": "^5.8.0",
"@testing-library/jest-dom": "^5.16.4", "@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.0.1", "@testing-library/react": "^13.0.1",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
@ -21,7 +26,12 @@
"watch": "npm-watch" "watch": "npm-watch"
}, },
"watch": { "watch": {
"build": "src/" "build": {
"patterns": [
"src/"
],
"extensions": "js,jsx"
}
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [

View File

@ -12,6 +12,7 @@ function App() {
const [user, setUser] = useState(null); const [user, setUser] = useState(null);
const [gamenights, setGamenights] = useState([]); const [gamenights, setGamenights] = useState([]);
const [flashData, setFlashData] = useState({}); const [flashData, setFlashData] = useState({});
const [games, setGames] = useState([]);
const handleLogin = (input) => { const handleLogin = (input) => {
const requestOptions = { const requestOptions = {
@ -68,6 +69,27 @@ function App() {
} }
}, [user]) }, [user])
useEffect(() => {
if (user !== null) {
const requestOptions = {
method: 'GET',
headers: { 'Authorization': `Bearer ${user.jwt}` },
};
fetch('api/games', requestOptions)
.then(response => response.json())
.then(data => {
if(data.result === "Ok") {
setGames(data.games)
} else {
setFlashData({
type: "Error",
message: data.message
});
}
});
}
}, [user])
useEffect(() => { useEffect(() => {
setUser(JSON.parse(localStorage.getItem(localStorageUserKey))); setUser(JSON.parse(localStorage.getItem(localStorageUserKey)));
}, []); }, []);
@ -82,7 +104,7 @@ function App() {
return ( return (
<> <>
<MenuBar user={user} onLogout={onLogout} /> <MenuBar user={user} onLogout={onLogout} />
<AddGameNight user={user} setFlash={setFlash} refetchGamenights={refetchGamenights} /> <AddGameNight user={user} games={games} setFlash={setFlash} refetchGamenights={refetchGamenights} />
<Gamenights user={user} setFlash={setFlash} refetchGamenights={refetchGamenights} gamenights={gamenights} /> <Gamenights user={user} setFlash={setFlash} refetchGamenights={refetchGamenights} gamenights={gamenights} />
</> </>
); );

View File

@ -1,5 +1,8 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import DateTime from 'react-datetime'; import DateTime from 'react-datetime';
import GameAdder from './GameAdder';
import Autocomplete from '@mui/material/Autocomplete';
import "react-datetime/css/react-datetime.css"; import "react-datetime/css/react-datetime.css";
@ -7,15 +10,26 @@ function AddGameNight(props) {
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
const [gameName, setGameName] = useState(""); const [gameName, setGameName] = useState("");
const [date, setDate] = useState(Date.now()); const [date, setDate] = useState(Date.now());
const [gameList, setGameList] = useState([]);
const emptyUuid = "00000000-0000-0000-0000-000000000000";
//temp hack:
props.games = [{id: emptyUuid, name: "mystic vale"}, {id: emptyUuid, name: "Crew"}];
const handleNameChange = (event) => { const handleNameChange = (event) => {
setGameName(event.target.value); setGameName(event.target.value);
}; };
const handleDateChange = (event) => { const onDateChange = (date) => {
setDate(event.target.value); setDate(date);
}; };
const onGamesListChange = (gameList) => {
setGameList(gameList);
}
useEffect(() => { useEffect(() => {
if(!expanded) { if(!expanded) {
setGameName(""); setGameName("");
@ -27,8 +41,9 @@ function AddGameNight(props) {
event.preventDefault(); event.preventDefault();
if (props.user !== null) { if (props.user !== null) {
let input = { let input = {
game: gameName, name: gameName,
datetime: date, datetime: date,
game_list: gameList,
} }
const requestOptions = { const requestOptions = {
@ -40,7 +55,7 @@ function AddGameNight(props) {
body: JSON.stringify(input) body: JSON.stringify(input)
}; };
fetch('api/gamenight', requestOptions) fetch('api/gamenights', requestOptions)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if(data.result !== "Ok") { if(data.result !== "Ok") {
@ -62,7 +77,7 @@ function AddGameNight(props) {
if(expanded) { if(expanded) {
return ( return (
<div className="Add-GameNight"> <div className="Add-GameNight">
<form> <form autoComplete="off" onSubmit={e => { e.preventDefault(); }}>
<fieldset> <fieldset>
<legend>Gamenight</legend> <legend>Gamenight</legend>
@ -71,8 +86,10 @@ function AddGameNight(props) {
value={gameName} value={gameName}
onChange={handleNameChange} /> onChange={handleNameChange} />
<label for="datetime">date:</label> <label for="datetime">date:</label>
<DateTime id="datetime" onChange={(value) => { setDate(value) }} value={date} /> <DateTime id="datetime" onChange={onDateChange} value={date} />
<GameAdder games={props.games} onChange={onGamesListChange}/>
<button onClick={handleAddGamenight}>Submit</button> <button onClick={handleAddGamenight}>Submit</button>
</fieldset> </fieldset>
</form> </form>

View File

@ -0,0 +1,60 @@
import * as React from 'react';
import TextField from '@mui/material/TextField';
import Chip from '@mui/material/Chip';
import Autocomplete from '@mui/material/Autocomplete';
export default function GameAdder(props) {
const [value, setValue] = React.useState([]);
const emptyUuid = "00000000-0000-0000-0000-000000000000";
return (
<Autocomplete
multiple
id="tags-filled"
options={props.games}
value={value}
getOptionLabel={(option) => option.name}
freeSolo
selectOnFocus
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip variant="outlined" label={option.name} {...getTagProps({ index })} />
))
}
renderInput={(params) => (
<TextField
{...params}
variant="filled"
label="What games do you like to play?"
placeholder="monopoly"
/>
)}
onChange={(event, newValue) => {
newValue = newValue.map(option => {
if (typeof option === 'string') {
var match = props.games.find(g => g.name.toLowerCase() === option.toLowerCase());
if(match !== undefined) {
return match
} else {
return {id: emptyUuid, name: option};
}
}
else {
return option;
}
});
newValue = newValue.filter((value, index, self) =>
index === self.findIndex((t) => (
t.id === value.id && t.name === value.name
))
);
setValue(newValue);
props.onChange(newValue);
}}
/>
);
}

View File

@ -17,7 +17,7 @@ function Gamenights(props) {
body: JSON.stringify(input) body: JSON.stringify(input)
}; };
fetch('api/gamenight', requestOptions) fetch('api/gamenights', requestOptions)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if(data.result !== "Ok") { if(data.result !== "Ok") {
@ -34,7 +34,7 @@ function Gamenights(props) {
let gamenights = props.gamenights.map(g => let gamenights = props.gamenights.map(g =>
( (
<li> <li>
<span>{g.game}</span> <span>{g.name}</span>
{(props.user.id === g.owner_id || props.user.role === "Admin") && {(props.user.id === g.owner_id || props.user.role === "Admin") &&
<button onClick={() =>DeleteGameNight(g.id, g.owner)}> <button onClick={() =>DeleteGameNight(g.id, g.owner)}>
x x