Added a user system with no proper user validation but working authorisation. #1
3
.gitignore
vendored
@ -1,4 +1 @@
|
||||
/target
|
||||
.vscode
|
||||
Rocket.toml
|
||||
*.sqlite
|
||||
|
4
backend/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/target
|
||||
.vscode
|
||||
App.toml
|
||||
*.sqlite
|
@ -1,4 +1,8 @@
|
||||
#Copy this file over to Rocket.toml after changing all relevant values.
|
||||
|
||||
[default]
|
||||
jwt_secret = "some really good secret"
|
||||
|
||||
[global.databases]
|
||||
gamenight_database = { url = "gamenight.sqlite" }
|
||||
|
447
Cargo.lock → backend/Cargo.lock
generated
@ -2,6 +2,15 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
@ -11,6 +20,17 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a27e27b63e4a34caee411ade944981136fdfa535522dc9944d6700196cbd899f"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.3"
|
||||
@ -75,6 +95,18 @@ version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179"
|
||||
|
||||
[[package]]
|
||||
name = "binascii"
|
||||
version = "0.1.4"
|
||||
@ -87,6 +119,15 @@ version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388"
|
||||
dependencies = [
|
||||
"digest 0.10.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.7.3"
|
||||
@ -96,7 +137,16 @@ dependencies = [
|
||||
"block-padding",
|
||||
"byte-tools",
|
||||
"byteorder",
|
||||
"generic-array",
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
|
||||
dependencies = [
|
||||
"generic-array 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -180,6 +230,16 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
|
||||
dependencies = [
|
||||
"generic-array 0.14.5",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "devise"
|
||||
version = "0.3.1"
|
||||
@ -225,6 +285,18 @@ dependencies = [
|
||||
"r2d2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel-derive-enum"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c8910921b014e2af16298f006de12aa08af894b71f0f49a486ab6d74b17bbed"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel_derives"
|
||||
version = "1.4.1"
|
||||
@ -252,7 +324,18 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
||||
dependencies = [
|
||||
"block-buffer 0.10.2",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -323,6 +406,16 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent"
|
||||
version = "0.4.0"
|
||||
@ -451,14 +544,20 @@ dependencies = [
|
||||
name = "gamenight"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"chrono",
|
||||
"diesel",
|
||||
"diesel-derive-enum",
|
||||
"diesel_migrations",
|
||||
"jsonwebtoken",
|
||||
"libsqlite3-sys",
|
||||
"password-hash",
|
||||
"rand_core",
|
||||
"rocket",
|
||||
"rocket_dyn_templates",
|
||||
"rocket_sync_db_pools",
|
||||
"serde",
|
||||
"validator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -483,6 +582,16 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.5"
|
||||
@ -539,6 +648,12 @@ version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@ -606,6 +721,23 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "if_chain"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.8.0"
|
||||
@ -667,6 +799,29 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonwebtoken"
|
||||
version = "8.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc9051c17f81bae79440afa041b3a278e1de71bfb96d32454b477fd4703ccb6f"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"pem",
|
||||
"ring",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"simple_asn1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
@ -754,6 +909,12 @@ dependencies = [
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
@ -867,7 +1028,7 @@ dependencies = [
|
||||
"log",
|
||||
"memchr",
|
||||
"mime",
|
||||
"spin",
|
||||
"spin 0.9.2",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"version_check",
|
||||
@ -920,6 +1081,17 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.44"
|
||||
@ -949,6 +1121,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.10.0"
|
||||
@ -986,6 +1167,17 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa26fd5c3cd6e6bb83dd9c0cef40fbeb77d7596339ca46c18a6f66919bb07769"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pear"
|
||||
version = "0.2.3"
|
||||
@ -1009,6 +1201,15 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
@ -1082,6 +1283,30 @@ version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
@ -1116,6 +1341,15 @@ version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quickcheck"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
|
||||
dependencies = [
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.16"
|
||||
@ -1201,6 +1435,8 @@ version = "1.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
@ -1228,6 +1464,21 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin 0.5.2",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rocket"
|
||||
version = "0.5.0-rc.1"
|
||||
@ -1453,8 +1704,8 @@ version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"digest",
|
||||
"block-buffer 0.7.3",
|
||||
"digest 0.8.1",
|
||||
"fake-simd",
|
||||
"opaque-debug",
|
||||
]
|
||||
@ -1492,6 +1743,18 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simple_asn1"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a762b1c38b9b990c694b9c2f8abe3372ce6a9ceaae6bca39cfc46e054f45745"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"thiserror",
|
||||
"time 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.5"
|
||||
@ -1514,6 +1777,12 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.9.2"
|
||||
@ -1596,6 +1865,12 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.89"
|
||||
@ -1621,6 +1896,26 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.4"
|
||||
@ -1651,11 +1946,24 @@ dependencies = [
|
||||
"libc",
|
||||
"standback",
|
||||
"stdweb",
|
||||
"time-macros",
|
||||
"time-macros 0.1.1",
|
||||
"version_check",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"quickcheck",
|
||||
"time-macros 0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.1.1"
|
||||
@ -1666,6 +1974,12 @@ dependencies = [
|
||||
"time-macros-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros-impl"
|
||||
version = "0.1.2"
|
||||
@ -1679,6 +1993,21 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.17.0"
|
||||
@ -1848,12 +2177,88 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d0f08911ab0fee2c5009580f04615fa868898ee57de10692a45da0c3bcc3e5e"
|
||||
dependencies = [
|
||||
"idna",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"url",
|
||||
"validator_derive",
|
||||
"validator_types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator_derive"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d85135714dba11a1bd0b3eb1744169266f1a38977bf4e3ff5e2e1acb8c2b7eee"
|
||||
dependencies = [
|
||||
"if_chain",
|
||||
"lazy_static",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn",
|
||||
"validator_types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator_types"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ded9d97e1d42327632f5f3bae6403c04886e2de3036261ef42deebd931a6a291"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
@ -1907,9 +2312,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.79"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||
checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
@ -1917,9 +2322,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.79"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||
checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
@ -1932,9 +2337,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.79"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||
checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@ -1942,9 +2347,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.79"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||
checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1955,9 +2360,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.79"
|
||||
version = "0.2.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||
checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.57"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
@ -15,4 +15,10 @@ diesel_migrations = "1.4.0"
|
||||
rocket_dyn_templates = { version = "0.1.0-rc.1", features = ["handlebars"] }
|
||||
chrono = "0.4.19"
|
||||
serde = "1.0.136"
|
||||
password-hash = "0.4"
|
||||
argon2 = "0.4"
|
||||
rand_core = { version = "0.6", features = ["std"] }
|
||||
diesel-derive-enum = { version = "1.1", features = ["sqlite"] }
|
||||
jsonwebtoken = "8.1"
|
||||
validator = { version = "0.14", features = ["derive"] }
|
||||
|
@ -0,0 +1,4 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
|
||||
drop table pwd;
|
||||
drop table user;
|
12
backend/migrations/2022-04-17-175115_user-system/up.sql
Normal file
@ -0,0 +1,12 @@
|
||||
CREATE TABLE user (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
role TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE pwd (
|
||||
user_id INTEGER NOT NULL PRIMARY KEY,
|
||||
Roflin marked this conversation as resolved
Outdated
|
||||
password TEXT NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
3
backend/requests/gamenights.sh
Executable file
@ -0,0 +1,3 @@
|
||||
echo $JWT
|
||||
|
||||
curl -X GET -H "Authorization: Bearer ${JWT}" localhost:8000/api/gamenights
|
1
backend/requests/login.sh
Executable file
@ -0,0 +1 @@
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"username": "a", "password": "c"}' localhost:8000/api/login
|
1
backend/requests/register.sh
Executable file
@ -0,0 +1 @@
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"username": "roflin", "email": "user@example.com", "password": "oreokoekje123", "password_repeat": "oreokoekje123"}' localhost:8000/api/register
|
195
backend/src/api.rs
Normal file
@ -0,0 +1,195 @@
|
||||
use crate::schema;
|
||||
use crate::schema::DbConn;
|
||||
use crate::AppConfig;
|
||||
use chrono::Utc;
|
||||
use jsonwebtoken::decode;
|
||||
use jsonwebtoken::encode;
|
||||
use jsonwebtoken::DecodingKey;
|
||||
use jsonwebtoken::Validation;
|
||||
use jsonwebtoken::{EncodingKey, Header};
|
||||
use rocket::http::Status;
|
||||
use rocket::request::Outcome;
|
||||
use rocket::request::{FromRequest, Request};
|
||||
use rocket::serde::json::{json, Json, Value};
|
||||
use rocket::State;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
use validator::ValidateArgs;
|
||||
|
||||
#[derive(Debug, Responder)]
|
||||
pub enum ApiResponseVariant {
|
||||
Lucus
commented
You can probably use a You can probably use a `Result<Value, Status>` for most endpoints and avoid a custom enum. I also recommend using `json::Value` qualified like that because `Value` by itself is not very descriptive.
Roflin
commented
True, but in the future we might want to return a status on a non error condition, or return a Redirect, I understand it is a bit overkill now, but in a previous iteration I was also returning Redirects and then this becomes a nice solution imho. True, but in the future we might want to return a status on a non error condition, or return a Redirect, I understand it is a bit overkill now, but in a previous iteration I was also returning Redirects and then this becomes a nice solution imho.
|
||||
Status(Status),
|
||||
// Redirect(Redirect),
|
||||
Value(Value),
|
||||
// Flash(Flash<Redirect>)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct ApiResponse {
|
||||
result: Cow<'static, str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
message: Option<Cow<'static, str>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
jwt: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
impl ApiResponse {
|
||||
const SUCCES_RESULT: Cow<'static, str> = Cow::Borrowed("Ok");
|
||||
const FAILURE_RESULT: Cow<'static, str> = Cow::Borrowed("Failure");
|
||||
|
||||
const SUCCES: Self = Self {
|
||||
result: Self::SUCCES_RESULT,
|
||||
message: None,
|
||||
jwt: None,
|
||||
};
|
||||
|
||||
fn error(message: String) -> Self {
|
||||
Self {
|
||||
result: Self::FAILURE_RESULT,
|
||||
message: Some(Cow::Owned(message)),
|
||||
jwt: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn login_response(jwt: String) -> Self {
|
||||
Self {
|
||||
result: Self::SUCCES_RESULT,
|
||||
message: None,
|
||||
jwt: Some(Cow::Owned(jwt)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ApiError {
|
||||
RequestError(String),
|
||||
}
|
||||
|
||||
const AUTH_HEADER: &str = "Authorization";
|
||||
const BEARER: &str = "Bearer ";
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for schema::User {
|
||||
type Error = ApiError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
let header = match req.headers().get_one(AUTH_HEADER) {
|
||||
Some(header) => header,
|
||||
None => {
|
||||
return Outcome::Forward(())
|
||||
}
|
||||
};
|
||||
|
||||
if !header.starts_with(BEARER) {
|
||||
return Outcome::Forward(());
|
||||
};
|
||||
|
||||
let app_config = req.guard::<&State<AppConfig>>().await.unwrap().inner();
|
||||
let jwt = header.trim_start_matches(BEARER).to_owned();
|
||||
let token = match decode::<Claims>(
|
||||
&jwt,
|
||||
&DecodingKey::from_secret(app_config.jwt_secret.as_bytes()),
|
||||
&Validation::default(),
|
||||
) {
|
||||
Ok(token) => token,
|
||||
Err(_) => {
|
||||
return Outcome::Forward(())
|
||||
}
|
||||
};
|
||||
let id = token.claims.uid;
|
||||
|
||||
let conn = req.guard::<DbConn>().await.unwrap();
|
||||
return Outcome::Success(schema::get_user(conn, id).await);
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/gamenights")]
|
||||
pub async fn gamenights(conn: DbConn, _user: schema::User) -> ApiResponseVariant {
|
||||
let gamenights = schema::get_all_gamenights(conn).await;
|
||||
ApiResponseVariant::Value(json!(gamenights))
|
||||
}
|
||||
|
||||
#[get("/gamenights", rank = 2)]
|
||||
pub async fn gamenights_unauthorized() -> ApiResponseVariant {
|
||||
ApiResponseVariant::Status(Status::Unauthorized)
|
||||
}
|
||||
|
||||
Roflin marked this conversation as resolved
Lucus
commented
I think you can use a Request Guard (see https://api.rocket.rs/v0.5-rc/rocket/request/trait.FromRequest.html) to authenticate the user and role: For example, endpoints that require admin privileges could accept a non-optional I think you can use a Request Guard (see https://api.rocket.rs/v0.5-rc/rocket/request/trait.FromRequest.html) to authenticate the user and role: For example, endpoints that require admin privileges could accept a non-optional `Admin` struct containing a user id and the request guard that generates it would only return `Success` if the user is logged and has the admin role.
Lucus
commented
See also the examples under the header "Request-Local State" in the above link. See also the examples under the header "Request-Local State" in the above link.
Lucus
commented
Reading more carefully I see you're already doing this, just that you're accepting an Reading more carefully I see you're already doing this, just that you're accepting an `Option<User>` and then checking it's not `None` while you could accept a `User` and be sure.
|
||||
#[post("/gamenight", format = "application/json", data = "<gamenight_json>")]
|
||||
pub async fn gamenight_post_json(
|
||||
conn: DbConn,
|
||||
user: Option<schema::User>,
|
||||
gamenight_json: Json<schema::GameNightNoId>,
|
||||
) -> ApiResponseVariant {
|
||||
if user.is_some() {
|
||||
schema::insert_gamenight(conn, gamenight_json.into_inner()).await;
|
||||
ApiResponseVariant::Value(json!(ApiResponse::SUCCES))
|
||||
} else {
|
||||
ApiResponseVariant::Status(Status::Unauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
#[post("/register", format = "application/json", data = "<register_json>")]
|
||||
pub async fn register_post_json(
|
||||
conn: DbConn,
|
||||
register_json: Json<schema::Register>,
|
||||
) -> ApiResponseVariant {
|
||||
let register = register_json.into_inner();
|
||||
let register_clone = register.clone();
|
||||
match conn
|
||||
.run(move |c| register_clone.validate_args((c, c)))
|
||||
.await
|
||||
{
|
||||
Ok(()) => (),
|
||||
Err(error) => {
|
||||
return ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string())))
|
||||
}
|
||||
}
|
||||
|
||||
match schema::insert_user(conn, register).await {
|
||||
Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)),
|
||||
Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))),
|
||||
}
|
||||
}
|
||||
yorick
commented
Value is not an error response Value is not an error response
Roflin
commented
We'll it is, it's an application level error, so it's a valid request and you will get a valid http response with an "Failure" result. So that's why it returns an actual Json value We'll it is, it's an application level error, so it's a valid request and you will get a valid http response with an "Failure" result. So that's why it returns an actual Json value
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct Claims {
|
||||
exp: i64,
|
||||
uid: i32,
|
||||
role: schema::Role,
|
||||
yorick
commented
Value is not an error response Value is not an error response
|
||||
}
|
||||
|
||||
#[post("/login", format = "application/json", data = "<login_json>")]
|
||||
pub async fn login_post_json(
|
||||
conn: DbConn,
|
||||
config: &State<AppConfig>,
|
||||
login_json: Json<schema::Login>,
|
||||
) -> ApiResponseVariant {
|
||||
match schema::login(conn, login_json.into_inner()).await {
|
||||
Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))),
|
||||
Ok(login_result) => {
|
||||
if !login_result.result {
|
||||
return ApiResponseVariant::Value(json!(ApiResponse::error(String::from(
|
||||
"username and password didn't match"
|
||||
))));
|
||||
}
|
||||
|
||||
let my_claims = Claims {
|
||||
exp: Utc::now().timestamp() + chrono::Duration::days(7).num_seconds(),
|
||||
uid: login_result.id.unwrap(),
|
||||
role: login_result.role.unwrap(),
|
||||
};
|
||||
|
||||
let secret = &config.inner().jwt_secret;
|
||||
match encode(
|
||||
&Header::default(),
|
||||
&my_claims,
|
||||
&EncodingKey::from_secret(secret.as_bytes()),
|
||||
) {
|
||||
Ok(token) => ApiResponseVariant::Value(json!(ApiResponse::login_response(token))),
|
||||
Err(error) => {
|
||||
ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string())))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
backend/src/main.rs
Normal file
@ -0,0 +1,69 @@
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
#[macro_use]
|
||||
extern crate diesel_migrations;
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
|
||||
use rocket::{
|
||||
fairing::AdHoc,
|
||||
figment::{
|
||||
providers::{Env, Format, Serialized, Toml},
|
||||
Figment, Profile,
|
||||
},
|
||||
};
|
||||
use rocket_dyn_templates::Template;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
mod api;
|
||||
pub mod schema;
|
||||
mod site;
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct AppConfig {
|
||||
jwt_secret: String,
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
fn default() -> AppConfig {
|
||||
AppConfig {
|
||||
jwt_secret: String::from("secret"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
let figment = Figment::from(rocket::Config::default())
|
||||
.merge(Serialized::defaults(AppConfig::default()))
|
||||
.merge(Toml::file("App.toml").nested())
|
||||
.merge(Env::prefixed("APP_").global())
|
||||
.select(Profile::from_env_or("APP_PROFILE", "default"));
|
||||
|
||||
let rocket = rocket::custom(figment)
|
||||
.attach(schema::DbConn::fairing())
|
||||
.attach(Template::fairing())
|
||||
.attach(AdHoc::on_ignite("Run Migrations", schema::run_migrations))
|
||||
.attach(AdHoc::config::<AppConfig>())
|
||||
.mount(
|
||||
"/",
|
||||
routes![
|
||||
site::index,
|
||||
site::gamenights,
|
||||
site::add_game_night,
|
||||
site::register
|
||||
],
|
||||
)
|
||||
.mount(
|
||||
"/api",
|
||||
routes![
|
||||
api::gamenights,
|
||||
api::gamenights_unauthorized,
|
||||
api::gamenight_post_json,
|
||||
api::register_post_json,
|
||||
api::login_post_json
|
||||
],
|
||||
);
|
||||
|
||||
rocket
|
||||
}
|
319
backend/src/schema.rs
Normal file
@ -0,0 +1,319 @@
|
||||
use crate::diesel::BoolExpressionMethods;
|
||||
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::dsl::count;
|
||||
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::SqliteConnection);
|
||||
|
||||
impl Deref for DbConn {
|
||||
type Target = rocket_sync_db_pools::Connection<DbConn, diesel::SqliteConnection>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
gamenight (id) {
|
||||
id -> Integer,
|
||||
game -> Text,
|
||||
datetime -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
known_games (game) {
|
||||
id -> Integer,
|
||||
game -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
use diesel::sql_types::Integer;
|
||||
use diesel::sql_types::Text;
|
||||
use super::RoleMapping;
|
||||
user(id) {
|
||||
id -> Integer,
|
||||
username -> Text,
|
||||
email -> Text,
|
||||
role -> RoleMapping,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
pwd(user_id) {
|
||||
user_id -> Integer,
|
||||
password -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
allow_tables_to_appear_in_same_query!(gamenight, known_games,);
|
||||
|
||||
pub enum DatabaseError {
|
||||
Hash(password_hash::Error),
|
||||
Query(String),
|
||||
}
|
||||
|
||||
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) -> Vec<GameNight> {
|
||||
conn.run(|c| gamenight::table.load::<GameNight>(c).unwrap())
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNightNoId) -> () {
|
||||
conn.run(|c| {
|
||||
diesel::insert_into(gamenight::table)
|
||||
.values(new_gamenight)
|
||||
.execute(c)
|
||||
.unwrap()
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn insert_user(conn: DbConn, new_user: Register) -> Result<(), DatabaseError> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
|
||||
let argon2 = Argon2::default();
|
||||
|
||||
let password_hash = match argon2.hash_password(new_user.password.as_bytes(), &salt) {
|
||||
Ok(hash) => hash.to_string(),
|
||||
Err(error) => return Err(DatabaseError::Hash(error)),
|
||||
};
|
||||
|
||||
let user_insert_result = conn
|
||||
.run(move |c| {
|
||||
c.transaction(|| {
|
||||
diesel::insert_into(user::table)
|
||||
.values((
|
||||
user::username.eq(&new_user.username),
|
||||
user::email.eq(&new_user.email),
|
||||
user::role.eq(Role::User),
|
||||
))
|
||||
.execute(c)?;
|
||||
|
||||
let ids: Vec<i32> = match user::table
|
||||
.filter(
|
||||
user::username
|
||||
.eq(&new_user.username)
|
||||
.and(user::email.eq(&new_user.email)),
|
||||
)
|
||||
.select(user::id)
|
||||
Roflin marked this conversation as resolved
yorick
commented
called called `user_id` now
|
||||
.get_results(c)
|
||||
{
|
||||
Ok(id) => id,
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
|
||||
diesel::insert_into(pwd::table)
|
||||
.values((pwd::user_id.eq(ids[0]), pwd::password.eq(&password_hash)))
|
||||
Roflin marked this conversation as resolved
Outdated
Lucus
commented
Wel mooi om de grote expression waar je hier op matcht even een naam te geven zodat de match leesbaar blijft. Wel mooi om de grote expression waar je hier op matcht even een naam te geven zodat de match leesbaar blijft.
|
||||
.execute(c)
|
||||
})
|
||||
})
|
||||
.await;
|
||||
|
||||
match user_insert_result {
|
||||
Err(e) => Err(DatabaseError::Query(e.to_string())),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn login(conn: DbConn, login: Login) -> Result<LoginResult, DatabaseError> {
|
||||
conn.run(move |c| -> Result<LoginResult, DatabaseError> {
|
||||
let id: i32 = match user::table
|
||||
.filter(user::username.eq(&login.username))
|
||||
.or_filter(user::email.eq(&login.username))
|
||||
.select(user::id)
|
||||
.first(c)
|
||||
{
|
||||
Ok(id) => id,
|
||||
Roflin marked this conversation as resolved
Outdated
yorick
commented
generates a panic if the user does not exist generates a panic if the user does not exist
|
||||
Err(error) => return Err(DatabaseError::Query(error.to_string())),
|
||||
};
|
||||
|
||||
let pwd: String = match pwd::table
|
||||
.filter(pwd::user_id.eq(id))
|
||||
.select(pwd::password)
|
||||
.first(c)
|
||||
{
|
||||
Ok(pwd) => pwd,
|
||||
Err(error) => return Err(DatabaseError::Query(error.to_string())),
|
||||
};
|
||||
|
||||
let parsed_hash = match PasswordHash::new(&pwd) {
|
||||
Ok(hash) => hash,
|
||||
Err(error) => return Err(DatabaseError::Hash(error)),
|
||||
};
|
||||
|
||||
if Argon2::default()
|
||||
.verify_password(&login.password.as_bytes(), &parsed_hash)
|
||||
.is_ok()
|
||||
{
|
||||
let role: Role = match user::table
|
||||
.filter(user::id.eq(id))
|
||||
.select(user::role)
|
||||
.first(c)
|
||||
{
|
||||
Ok(role) => role,
|
||||
Err(error) => return Err(DatabaseError::Query(error.to_string())),
|
||||
};
|
||||
|
||||
Ok(LoginResult {
|
||||
result: true,
|
||||
id: Some(id),
|
||||
role: Some(role),
|
||||
})
|
||||
} else {
|
||||
Ok(LoginResult {
|
||||
result: false,
|
||||
id: None,
|
||||
role: None,
|
||||
})
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_user(conn: DbConn, id: i32) -> User {
|
||||
conn.run(move |c| user::table.filter(user::id.eq(id)).first(c).unwrap())
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn unique_username(
|
||||
username: &String,
|
||||
conn: &diesel::SqliteConnection,
|
||||
) -> Result<(), ValidationError> {
|
||||
match user::table
|
||||
.select(count(user::username))
|
||||
.filter(user::username.eq(username))
|
||||
.execute(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::SqliteConnection,
|
||||
) -> Result<(), ValidationError> {
|
||||
match user::table
|
||||
.select(count(user::email))
|
||||
.filter(user::email.eq(email))
|
||||
.execute(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<Build>) -> Rocket<Build> {
|
||||
// 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)]
|
||||
pub enum Role {
|
||||
Admin,
|
||||
User,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)]
|
||||
#[table_name = "user"]
|
||||
pub struct User {
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
pub role: Role,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, FromForm, Insertable)]
|
||||
#[table_name = "known_games"]
|
||||
pub struct GameNoId {
|
||||
pub game: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)]
|
||||
pub struct Game {
|
||||
pub id: i32,
|
||||
pub game: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, FromForm, Insertable)]
|
||||
#[table_name = "gamenight"]
|
||||
pub struct GameNightNoId {
|
||||
pub game: String,
|
||||
pub datetime: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)]
|
||||
pub struct GameNight {
|
||||
pub id: i32,
|
||||
pub game: String,
|
||||
pub datetime: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Validate, Clone)]
|
||||
pub struct Register {
|
||||
#[validate(
|
||||
length(min = 1),
|
||||
custom(function = "unique_username", arg = "&'v_a diesel::SqliteConnection")
|
||||
)]
|
||||
pub username: String,
|
||||
#[validate(
|
||||
email,
|
||||
custom(function = "unique_email", arg = "&'v_a diesel::SqliteConnection")
|
||||
)]
|
||||
pub email: String,
|
||||
#[validate(length(min = 10), must_match = "password_repeat")]
|
||||
pub password: String,
|
||||
Roflin marked this conversation as resolved
yorick
commented
Ik zou password_repeat checks in de frontend doen, als je die dan toch hebt. Ik zou password_repeat checks in de frontend doen, als je die dan toch hebt.
Roflin
commented
Het plan is om beide te doen, want als je dan de frontend checks omzeilt krijg je alsnog narigheid. want je kan ook gewoon tegen de api aan praten. Het plan is om beide te doen, want als je dan de frontend checks omzeilt krijg je alsnog narigheid. want je kan ook gewoon tegen de api aan praten.
|
||||
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 id: Option<i32>,
|
||||
pub role: Option<Role>,
|
||||
}
|
90
backend/src/site.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::schema;
|
||||
use rocket::request::FlashMessage;
|
||||
use rocket::response::Redirect;
|
||||
Roflin marked this conversation as resolved
Outdated
yorick
commented
| ^^^^^^^^^^^ use of undeclared crate or module | ^^^^^^^^^^^ use of undeclared crate or module `rocket_cors`
|
||||
use rocket_dyn_templates::Template;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct FlashData {
|
||||
has_data: bool,
|
||||
kind: Cow<'static, str>,
|
||||
message: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl FlashData {
|
||||
const EMPTY: Self = Self {
|
||||
has_data: false,
|
||||
message: Cow::Borrowed(""),
|
||||
kind: Cow::Borrowed(""),
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct GameNightsData {
|
||||
gamenights: Vec<schema::GameNight>,
|
||||
flash: FlashData,
|
||||
}
|
||||
|
||||
#[get("/gamenights")]
|
||||
pub async fn gamenights(conn: schema::DbConn) -> Template {
|
||||
let gamenights = schema::get_all_gamenights(conn).await;
|
||||
|
||||
let data = GameNightsData {
|
||||
gamenights: gamenights,
|
||||
flash: FlashData::EMPTY,
|
||||
};
|
||||
|
||||
Template::render("gamenights", &data)
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index() -> Redirect {
|
||||
Redirect::to(uri!(gamenights))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct GameNightAddData {
|
||||
post_url: String,
|
||||
flash: FlashData,
|
||||
}
|
||||
|
||||
#[get("/gamenight/add")]
|
||||
pub async fn add_game_night(flash: Option<FlashMessage<'_>>) -> Template {
|
||||
let flash_data = match flash {
|
||||
None => FlashData::EMPTY,
|
||||
Some(flash) => FlashData {
|
||||
has_data: true,
|
||||
message: Cow::Owned(flash.message().to_string()),
|
||||
kind: Cow::Owned(flash.kind().to_string()),
|
||||
},
|
||||
};
|
||||
|
||||
let data = GameNightAddData {
|
||||
post_url: "/api/gamenight".to_string(),
|
||||
flash: flash_data,
|
||||
};
|
||||
|
||||
Template::render("gamenight_add", &data)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct RegisterData {
|
||||
flash: FlashData,
|
||||
}
|
||||
|
||||
#[get("/register")]
|
||||
pub async fn register(flash: Option<FlashMessage<'_>>) -> Template {
|
||||
let flash_data = match flash {
|
||||
None => FlashData::EMPTY,
|
||||
Some(flash) => FlashData {
|
||||
has_data: true,
|
||||
message: Cow::Owned(flash.message().to_string()),
|
||||
kind: Cow::Owned(flash.kind().to_string()),
|
||||
},
|
||||
};
|
||||
|
||||
let data = RegisterData { flash: flash_data };
|
||||
|
||||
Template::render("register", &data)
|
||||
}
|
19
backend/templates/register.html.hbs
Normal file
@ -0,0 +1,19 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
{{> flash flash }}
|
||||
|
||||
<form action="/api/register" method="post">
|
||||
<label for="username">Username:</label><br>
|
||||
<input type="text" id="username" name="username" required><br>
|
||||
<label for="email">Email:</label><br>
|
||||
<input type="text" id="email" name="email" required><br>
|
||||
<label for="password">Password:</label><br>
|
||||
<input type="password" id="password" name="password" required><br>
|
||||
<label for="password_repeat">Repeat password:</label><br>
|
||||
<input type="password" id="password_repeat" name="password_repeat" required><br>
|
||||
<input type="submit">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Roflin marked this conversation as resolved
Outdated
Lucus
commented
1 1
|
23
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
70
frontend/README.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `npm run eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `npm run build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
27260
frontend/package-lock.json
generated
Normal file
38
frontend/package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.0.1",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
BIN
frontend/public/favicon.ico
Normal file
After Width: | Height: | Size: 3.8 KiB |
43
frontend/public/index.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
Roflin marked this conversation as resolved
Outdated
yorick
commented
ex ex
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
BIN
frontend/public/logo192.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
frontend/public/logo512.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
25
frontend/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
frontend/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
38
frontend/src/App.css
Normal file
@ -0,0 +1,38 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
25
frontend/src/App.js
Normal file
@ -0,0 +1,25 @@
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
8
frontend/src/App.test.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
13
frontend/src/index.css
Normal file
@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
17
frontend/src/index.js
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
// If you want to start measuring performance in your app, pass a function
|
||||
// to log results (for example: reportWebVitals(console.log))
|
||||
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||
reportWebVitals();
|
1
frontend/src/logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
13
frontend/src/reportWebVitals.js
Normal file
@ -0,0 +1,13 @@
|
||||
const reportWebVitals = onPerfEntry => {
|
||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||
getCLS(onPerfEntry);
|
||||
getFID(onPerfEntry);
|
||||
getFCP(onPerfEntry);
|
||||
getLCP(onPerfEntry);
|
||||
getTTFB(onPerfEntry);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default reportWebVitals;
|
5
frontend/src/setupTests.js
Normal file
@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
58
src/api.rs
@ -1,58 +0,0 @@
|
||||
use crate::schema;
|
||||
use rocket::form::Form;
|
||||
use rocket::serde::json::{Json, json, Value};
|
||||
use rocket::http::Status;
|
||||
use rocket::request::{self, Request, FromRequest};
|
||||
use rocket::outcome::Outcome::{Success, Failure};
|
||||
use rocket::response::{Redirect, Flash};
|
||||
|
||||
pub struct Referer(String);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReferrerError {
|
||||
Missing,
|
||||
MoreThanOne
|
||||
}
|
||||
|
||||
#[derive(Debug, Responder)]
|
||||
pub enum ApiResponse {
|
||||
Status(Status),
|
||||
Redirect(Redirect),
|
||||
Value(Value),
|
||||
Flash(Flash<Redirect>)
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for Referer {
|
||||
type Error = ReferrerError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
|
||||
let referers : Vec<_> = req.headers().get("Referer").collect();
|
||||
match referers.len() {
|
||||
0 => Failure((Status::BadRequest, ReferrerError::Missing)),
|
||||
1 => Success(Referer(referers[0].to_string())),
|
||||
_ => Failure((Status::BadRequest, ReferrerError::MoreThanOne)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/gamenights")]
|
||||
pub async fn gamenights(conn: schema::DbConn) -> ApiResponse {
|
||||
let gamenights = schema::get_all_gamenights(conn).await;
|
||||
ApiResponse::Value(json!(gamenights))
|
||||
}
|
||||
|
||||
#[post("/gamenight", format = "application/json", data = "<gamenight_json>")]
|
||||
pub async fn gamenight_post_json(conn: schema::DbConn, gamenight_json: Json<schema::GameNightNoId>) -> ApiResponse {
|
||||
schema::insert_gamenight(conn, gamenight_json.into_inner()).await;
|
||||
ApiResponse::Status(Status::Accepted)
|
||||
}
|
||||
|
||||
#[post("/gamenight", format = "application/x-www-form-urlencoded", data = "<gamenight_form>")]
|
||||
pub async fn gamenight_post_form(referer: Option<Referer>, conn: schema::DbConn, gamenight_form: Form<schema::GameNightNoId>) -> ApiResponse {
|
||||
schema::insert_gamenight(conn, gamenight_form.into_inner()).await;
|
||||
match referer {
|
||||
None => ApiResponse::Status(Status::Accepted),
|
||||
Some(referer) => ApiResponse::Flash(Flash::success(Redirect::to(referer.0), "Added Gamenight."))
|
||||
}
|
||||
}
|
20
src/main.rs
@ -1,20 +0,0 @@
|
||||
#[macro_use] extern crate rocket;
|
||||
#[macro_use] extern crate diesel_migrations;
|
||||
#[macro_use] extern crate diesel;
|
||||
|
||||
use rocket::fairing::AdHoc;
|
||||
use rocket_dyn_templates::Template;
|
||||
|
||||
mod api;
|
||||
pub mod schema;
|
||||
mod site;
|
||||
|
||||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.attach(schema::DbConn::fairing())
|
||||
.attach(Template::fairing())
|
||||
.attach(AdHoc::on_ignite("Run Migrations", schema::run_migrations))
|
||||
.mount("/", routes![site::index, site::gamenights, site::add_game_night])
|
||||
.mount("/api", routes![api::gamenights, api::gamenight_post_form, api::gamenight_post_json])
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
use rocket_sync_db_pools::database;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rocket::{Rocket, Build};
|
||||
use diesel::RunQueryDsl;
|
||||
|
||||
|
||||
#[database("gamenight_database")]
|
||||
pub struct DbConn(diesel::SqliteConnection);
|
||||
|
||||
table! {
|
||||
gamenight (id) {
|
||||
id -> Integer,
|
||||
game -> Text,
|
||||
datetime -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
known_games (game) {
|
||||
id -> Integer,
|
||||
game -> Text,
|
||||
}
|
||||
}
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
gamenight,
|
||||
known_games,
|
||||
);
|
||||
|
||||
pub async fn get_all_gamenights(conn: DbConn) -> Vec::<GameNight> {
|
||||
conn.run(|c| {
|
||||
gamenight::table.load::<GameNight>(c).unwrap()
|
||||
}).await
|
||||
}
|
||||
|
||||
pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNightNoId) -> () {
|
||||
conn.run(|c| {
|
||||
diesel::insert_into(gamenight::table)
|
||||
.values(new_gamenight)
|
||||
.execute(c)
|
||||
.unwrap()
|
||||
}).await;
|
||||
}
|
||||
|
||||
pub async fn run_migrations(rocket: Rocket<Build>) -> Rocket<Build> {
|
||||
// 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(Serialize, Deserialize, Debug, FromForm, Insertable)]
|
||||
#[table_name="known_games"]
|
||||
pub struct GameNoId {
|
||||
pub game : String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)]
|
||||
pub struct Game {
|
||||
pub id: i32,
|
||||
pub game : String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, FromForm, Insertable)]
|
||||
#[table_name="gamenight"]
|
||||
pub struct GameNightNoId {
|
||||
pub game : String,
|
||||
pub datetime : String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)]
|
||||
pub struct GameNight {
|
||||
pub id: i32,
|
||||
pub game : String,
|
||||
pub datetime : String,
|
||||
}
|
||||
|
||||
|
57
src/site.rs
@ -1,57 +0,0 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rocket_dyn_templates::Template;
|
||||
use rocket::response::{Redirect};
|
||||
use rocket::request::{FlashMessage};
|
||||
use crate::schema;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct FlashData {
|
||||
has_data: bool,
|
||||
kind: String,
|
||||
message: String
|
||||
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct GameNightsData {
|
||||
gamenights: Vec::<schema::GameNight>,
|
||||
flash: FlashData
|
||||
}
|
||||
|
||||
#[get("/gamenights")]
|
||||
pub async fn gamenights(conn: schema::DbConn) -> Template {
|
||||
let gamenights = schema::get_all_gamenights(conn).await;
|
||||
|
||||
let data = GameNightsData {
|
||||
gamenights: gamenights,
|
||||
flash: FlashData { has_data: false, message: "".to_string(), kind: "".to_string() }
|
||||
};
|
||||
|
||||
Template::render("gamenights", &data)
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub async fn index() -> Redirect {
|
||||
Redirect::to(uri!(gamenights))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct GameNightAddData {
|
||||
post_url: String,
|
||||
flash : FlashData
|
||||
}
|
||||
|
||||
#[get("/gamenight/add")]
|
||||
pub async fn add_game_night(flash: Option<FlashMessage<'_>>) -> Template {
|
||||
let flash_data = match flash {
|
||||
None => FlashData { has_data: false, message: "".to_string(), kind: "".to_string() },
|
||||
Some(flash) => FlashData { has_data: true, message: flash.message().to_string(), kind: flash.kind().to_string() }
|
||||
};
|
||||
|
||||
let data = GameNightAddData {
|
||||
post_url: "/api/gamenight".to_string(),
|
||||
flash: flash_data
|
||||
};
|
||||
|
||||
Template::render("gamenight_add", &data)
|
||||
}
|
I recommend calling the column
user_id
in both tables. The column inpwd
should also have a foreign key constraint likeREFERENCES user ON DELETE CASCADE
. Or it could be in the same table: Using a separate table is usually only worth it if the rows are big or the relation is not one on one.