feat: Add game thumbnail fetching from external APIs, migrate

This commit is contained in:
code002lover 2025-12-04 15:15:47 +01:00
parent f49900adad
commit 04e21708b2
7 changed files with 882 additions and 59 deletions

691
Cargo.lock generated
View File

@ -110,6 +110,7 @@ dependencies = [
"prost", "prost",
"prost-build", "prost-build",
"prost-types", "prost-types",
"reqwest",
"rocket", "rocket",
"rocket_prost_responder_derive", "rocket_prost_responder_derive",
"serde", "serde",
@ -162,6 +163,12 @@ dependencies = [
"virtue", "virtue",
] ]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.10.0" version = "2.10.0"
@ -250,6 +257,22 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.21"
@ -312,13 +335,24 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.10.0",
"proc-macro2", "proc-macro2",
"proc-macro2-diagnostics", "proc-macro2-diagnostics",
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "dunce" name = "dunce"
version = "1.0.5" version = "1.0.5"
@ -400,6 +434,30 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
[[package]] [[package]]
name = "fs_extra" name = "fs_extra"
version = "1.3.0" version = "1.3.0"
@ -634,6 +692,19 @@ dependencies = [
"http", "http",
] ]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.10.1" version = "1.10.1"
@ -666,6 +737,39 @@ dependencies = [
"pin-utils", "pin-utils",
"smallvec", "smallvec",
"tokio", "tokio",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
] ]
[[package]] [[package]]
@ -674,13 +778,126 @@ version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56"
dependencies = [ dependencies = [
"base64",
"bytes", "bytes",
"futures-channel",
"futures-core", "futures-core",
"futures-util",
"http", "http",
"http-body", "http-body",
"hyper", "hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite", "pin-project-lite",
"socket2",
"system-configuration",
"tokio", "tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
name = "icu_collections"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99"
dependencies = [
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899"
[[package]]
name = "icu_provider"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
] ]
[[package]] [[package]]
@ -719,6 +936,22 @@ dependencies = [
"memoffset", "memoffset",
] ]
[[package]]
name = "ipnet"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "iri-string"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397"
dependencies = [
"memchr",
"serde",
]
[[package]] [[package]]
name = "is-terminal" name = "is-terminal"
version = "0.4.17" version = "0.4.17"
@ -789,6 +1022,12 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.14" version = "0.4.14"
@ -886,6 +1125,23 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.50.3" version = "0.50.3"
@ -946,6 +1202,50 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "openssl"
version = "0.10.75"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
dependencies = [
"bitflags 2.10.0",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.111"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.5" version = "0.12.5"
@ -1020,6 +1320,21 @@ 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.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "potential_utf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@ -1240,7 +1555,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.10.0",
] ]
[[package]] [[package]]
@ -1298,6 +1613,46 @@ version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
version = "0.12.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.17.14" version = "0.17.14"
@ -1410,7 +1765,7 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
dependencies = [ dependencies = [
"bitflags", "bitflags 2.10.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@ -1596,6 +1951,15 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "schannel"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
dependencies = [
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.1" version = "1.0.1"
@ -1608,6 +1972,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.10.0",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.228" version = "1.0.228"
@ -1660,6 +2047,18 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.7" version = "0.1.7"
@ -1727,6 +2126,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]] [[package]]
name = "state" name = "state"
version = "0.6.0" version = "0.6.0"
@ -1753,6 +2158,47 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.23.0" version = "3.23.0"
@ -1806,6 +2252,16 @@ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tinystr"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.10.0" version = "1.10.0"
@ -1849,6 +2305,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.4" version = "0.26.4"
@ -1924,6 +2390,51 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "tower"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456"
dependencies = [
"bitflags 2.10.0",
"bytes",
"futures-util",
"http",
"http-body",
"iri-string",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.43" version = "0.1.43"
@ -1986,6 +2497,12 @@ dependencies = [
"tracing-log", "tracing-log",
] ]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.19.0" version = "1.19.0"
@ -2041,6 +2558,24 @@ version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
[[package]]
name = "url"
version = "2.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.19.0" version = "1.19.0"
@ -2058,6 +2593,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
@ -2070,6 +2611,15 @@ version = "0.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.9.0+wasi-snapshot-preview1" version = "0.9.0+wasi-snapshot-preview1"
@ -2104,6 +2654,19 @@ dependencies = [
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c"
dependencies = [
"cfg-if",
"js-sys",
"once_cell",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.106" version = "0.2.106"
@ -2136,6 +2699,16 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "web-sys"
version = "0.3.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "windows" name = "windows"
version = "0.48.0" version = "0.48.0"
@ -2151,6 +2724,35 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.52.0"
@ -2379,6 +2981,12 @@ version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "writeable"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]] [[package]]
name = "yansi" name = "yansi"
version = "1.0.1" version = "1.0.1"
@ -2388,6 +2996,29 @@ dependencies = [
"is-terminal", "is-terminal",
] ]
[[package]]
name = "yoke"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.31" version = "0.8.31"
@ -2408,6 +3039,27 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.8.2" version = "1.8.2"
@ -2427,3 +3079,36 @@ dependencies = [
"quote", "quote",
"syn", "syn",
] ]
[[package]]
name = "zerotrie"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@ -16,6 +16,7 @@ bcrypt = "0.17.1"
bincode = "2.0.1" bincode = "2.0.1"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
reqwest = { version = "0.12.24", features = ["json"] }
[build-dependencies] [build-dependencies]
prost-build = "0.14.1" prost-build = "0.14.1"

View File

@ -1,8 +1,8 @@
use crate::items; use crate::items;
use crate::proto_utils::Proto; use crate::proto_utils::Proto;
use rocket::State; use rocket::State;
use rocket::futures::lock::Mutex;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Mutex;
use uuid::Uuid; use uuid::Uuid;
pub struct AuthState { pub struct AuthState {
@ -18,6 +18,12 @@ impl AuthState {
} }
} }
impl Default for AuthState {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug)] #[derive(Debug)]
#[allow(dead_code)] #[allow(dead_code)]
pub struct Token { pub struct Token {
@ -39,7 +45,7 @@ impl<'r> rocket::request::FromRequest<'r> for Token {
// Check if token starts with "Bearer " // Check if token starts with "Bearer "
if let Some(token) = token.strip_prefix("Bearer ") { if let Some(token) = token.strip_prefix("Bearer ") {
let state = request.guard::<&State<AuthState>>().await.unwrap(); let state = request.guard::<&State<AuthState>>().await.unwrap();
let tokens = state.tokens.lock().unwrap(); let tokens = state.tokens.lock().await;
if let Some(username) = tokens.get(token) { if let Some(username) = tokens.get(token) {
return rocket::request::Outcome::Success(Token { return rocket::request::Outcome::Success(Token {
@ -57,19 +63,19 @@ impl<'r> rocket::request::FromRequest<'r> for Token {
} }
#[post("/login", data = "<request>")] #[post("/login", data = "<request>")]
pub fn login( pub async fn login(
state: &State<AuthState>, state: &State<AuthState>,
user_list: &State<Mutex<Vec<crate::User>>>, user_list: &State<Mutex<Vec<crate::User>>>,
request: Proto<items::LoginRequest>, request: Proto<items::LoginRequest>,
) -> items::LoginResponse { ) -> items::LoginResponse {
let req = request.into_inner(); let req = request.into_inner();
let users = user_list.lock().unwrap(); let users = user_list.lock().await;
if let Some(user) = users.iter().find(|u| u.person.name == req.username) if let Some(user) = users.iter().find(|u| u.person.name == req.username)
&& bcrypt::verify(&req.password, &user.password_hash).unwrap_or(false) && bcrypt::verify(&req.password, &user.password_hash).unwrap_or(false)
{ {
let token = Uuid::new_v4().to_string(); let token = Uuid::new_v4().to_string();
let mut tokens = state.tokens.lock().unwrap(); let mut tokens = state.tokens.lock().await;
tokens.insert(token.clone(), req.username); tokens.insert(token.clone(), req.username);
return items::LoginResponse { return items::LoginResponse {
@ -87,12 +93,12 @@ pub fn login(
} }
#[post("/logout", data = "<request>")] #[post("/logout", data = "<request>")]
pub fn logout( pub async fn logout(
state: &State<AuthState>, state: &State<AuthState>,
request: Proto<items::LogoutRequest>, request: Proto<items::LogoutRequest>,
) -> items::LogoutResponse { ) -> items::LogoutResponse {
let req = request.into_inner(); let req = request.into_inner();
let mut tokens = state.tokens.lock().unwrap(); let mut tokens = state.tokens.lock().await;
if tokens.remove(&req.token).is_some() { if tokens.remove(&req.token).is_some() {
items::LogoutResponse { items::LogoutResponse {
@ -108,12 +114,12 @@ pub fn logout(
} }
#[post("/get_auth_status", data = "<request>")] #[post("/get_auth_status", data = "<request>")]
pub fn get_auth_status( pub async fn get_auth_status(
state: &State<AuthState>, state: &State<AuthState>,
request: Proto<items::AuthStatusRequest>, request: Proto<items::AuthStatusRequest>,
) -> items::AuthStatusResponse { ) -> items::AuthStatusResponse {
let req = request.into_inner(); let req = request.into_inner();
let tokens = state.tokens.lock().unwrap(); let tokens = state.tokens.lock().await;
if let Some(username) = tokens.get(&req.token) { if let Some(username) = tokens.get(&req.token) {
items::AuthStatusResponse { items::AuthStatusResponse {

View File

@ -1,5 +1,5 @@
use rocket::fs::FileServer; use rocket::fs::FileServer;
use std::sync::Mutex; use rocket::futures::lock::Mutex;
use backend::auth; use backend::auth;
use backend::items::{self, Game}; use backend::items::{self, Game};
@ -10,12 +10,12 @@ use backend::store::{self, User, save_state};
extern crate rocket; extern crate rocket;
#[get("/<name>")] #[get("/<name>")]
fn get_user( async fn get_user(
_token: auth::Token, _token: auth::Token,
user_list: &rocket::State<Mutex<Vec<User>>>, user_list: &rocket::State<Mutex<Vec<User>>>,
name: &str, name: &str,
) -> Option<items::Person> { ) -> Option<items::Person> {
let users = user_list.lock().unwrap(); let users = user_list.lock().await;
users users
.iter() .iter()
.find(|user| user.person.name == name) .find(|user| user.person.name == name)
@ -23,42 +23,45 @@ fn get_user(
} }
#[get("/")] #[get("/")]
fn get_users( async fn get_users(
_token: auth::Token, _token: auth::Token,
user_list: &rocket::State<Mutex<Vec<User>>>, user_list: &rocket::State<Mutex<Vec<User>>>,
) -> items::PersonList { ) -> items::PersonList {
let users = user_list.lock().unwrap(); let users = user_list.lock().await;
items::PersonList { items::PersonList {
person: users.iter().map(|u| u.person.clone()).collect(), person: users.iter().map(|u| u.person.clone()).collect(),
} }
} }
#[get("/game/<title>")] #[get("/game/<title>")]
fn get_game( async fn get_game(
_token: auth::Token, _token: auth::Token,
game_list: &rocket::State<Mutex<Vec<Game>>>, game_list: &rocket::State<Mutex<Vec<Game>>>,
title: &str, title: &str,
) -> Option<items::Game> { ) -> Option<items::Game> {
let games = game_list.lock().unwrap(); let games = game_list.lock().await;
games.iter().find(|g| g.title == title).cloned() games.iter().find(|g| g.title == title).cloned()
} }
#[get("/games")] #[get("/games")]
fn get_games(_token: auth::Token, game_list: &rocket::State<Mutex<Vec<Game>>>) -> items::GameList { async fn get_games(
let games = game_list.lock().unwrap(); _token: auth::Token,
game_list: &rocket::State<Mutex<Vec<Game>>>,
) -> items::GameList {
let games = game_list.lock().await;
items::GameList { items::GameList {
games: games.clone(), games: games.clone(),
} }
} }
#[post("/game", data = "<game>")] #[post("/game", data = "<game>")]
fn add_game( async fn add_game(
_token: auth::Token, _token: auth::Token,
game_list: &rocket::State<Mutex<Vec<Game>>>, game_list: &rocket::State<Mutex<Vec<Game>>>,
user_list: &rocket::State<Mutex<Vec<User>>>, user_list: &rocket::State<Mutex<Vec<User>>>,
game: proto_utils::Proto<items::Game>, game: proto_utils::Proto<items::Game>,
) -> Option<items::Game> { ) -> Option<items::Game> {
let mut games = game_list.lock().unwrap(); let mut games = game_list.lock().await;
let game = game.into_inner(); let game = game.into_inner();
if games.iter().any(|g| g.title == game.title) { if games.iter().any(|g| g.title == game.title) {
@ -67,21 +70,21 @@ fn add_game(
games.push(game.clone()); games.push(game.clone());
let users = user_list.lock().unwrap(); let users = user_list.lock().await;
save_state(&games, &users); save_state(&games, &users);
Some(game) Some(game)
} }
#[post("/opinion", data = "<req>")] #[post("/opinion", data = "<req>")]
fn add_opinion( async fn add_opinion(
token: auth::Token, token: auth::Token,
user_list: &rocket::State<Mutex<Vec<User>>>, user_list: &rocket::State<Mutex<Vec<User>>>,
game_list: &rocket::State<Mutex<Vec<Game>>>, game_list: &rocket::State<Mutex<Vec<Game>>>,
req: proto_utils::Proto<items::AddOpinionRequest>, req: proto_utils::Proto<items::AddOpinionRequest>,
) -> Option<items::Person> { ) -> Option<items::Person> {
let mut users = user_list.lock().unwrap(); let mut users = user_list.lock().await;
let games = game_list.lock().unwrap(); let games = game_list.lock().await;
let mut result = None; let mut result = None;
// Validate game exists // Validate game exists
@ -115,6 +118,68 @@ fn add_opinion(
result result
} }
#[get("/game_thumbnail/<title>")]
async fn get_game_thumbnail(
title: &str,
game_list: &rocket::State<Mutex<Vec<Game>>>,
) -> Option<(rocket::http::ContentType, Vec<u8>)> {
let games = game_list.lock().await;
let game = games.iter().find(|g| g.title == title)?;
let url = match items::Source::try_from(game.source).ok()? {
items::Source::Steam => format!(
"https://cdn.cloudflare.steamstatic.com/steam/apps/{}/header.jpg",
game.remote_id
),
items::Source::Roblox => {
let universe_id = {
let api_url = format!(
"https://apis.roblox.com/universes/v1/places/{}/universe",
game.remote_id
);
reqwest::get(&api_url)
.await
.ok()?
.json::<serde_json::Value>()
.await
.ok()?
.get("universeId")?
.as_u64()
.unwrap()
};
let api_url = format!(
"https://thumbnails.roblox.com/v1/games/icons?universeIds={}&size=512x512&format=Webp&isCircular=false",
universe_id
);
match reqwest::get(&api_url).await {
Ok(resp) => {
if let Ok(json) = resp.json::<serde_json::Value>().await {
json["data"][0]["imageUrl"].as_str()?.to_string()
} else {
return None;
}
}
Err(_) => return None,
}
}
};
match reqwest::get(&url).await {
Ok(resp) => {
let content_type = resp
.headers()
.get(reqwest::header::CONTENT_TYPE)
.and_then(|v| v.to_str().ok())
.and_then(rocket::http::ContentType::parse_flexible)
.unwrap_or(rocket::http::ContentType::Binary);
let bytes = resp.bytes().await.ok()?.to_vec();
Some((content_type, bytes))
}
Err(_) => None,
}
}
#[get("/<_..>", rank = 20)] #[get("/<_..>", rank = 20)]
async fn index_fallback() -> Option<rocket::fs::NamedFile> { async fn index_fallback() -> Option<rocket::fs::NamedFile> {
// Try multiple paths for robustness // Try multiple paths for robustness
@ -139,16 +204,31 @@ async fn main() -> Result<(), std::io::Error> {
min_players: 1, min_players: 1,
max_players: 90, max_players: 90,
price: 0, price: 0,
remote_id: 0, remote_id: 6032337657, // Universe ID for Naramo Nuclear Plant V2
});
game_list.push(Game {
title: "Terraria".to_string(),
source: items::Source::Steam.into(),
min_players: 1,
max_players: 8,
price: 999,
remote_id: 105600, // App ID for Terraria
}); });
user_list.push(User { user_list.push(User {
person: items::Person { person: items::Person {
name: "John".to_string(), name: "John".to_string(),
opinion: vec![items::Opinion { opinion: vec![
title: "Naramo Nuclear Plant V2".to_string(), items::Opinion {
would_play: true, title: "Naramo Nuclear Plant V2".to_string(),
}], would_play: true,
},
items::Opinion {
title: "Terraria".to_string(),
would_play: true,
},
],
}, },
password_hash: bcrypt::hash("password123", bcrypt::DEFAULT_COST).unwrap(), password_hash: bcrypt::hash("password123", bcrypt::DEFAULT_COST).unwrap(),
}); });
@ -167,7 +247,8 @@ async fn main() -> Result<(), std::io::Error> {
get_game, get_game,
get_games, get_games,
add_opinion, add_opinion,
add_game add_game,
get_game_thumbnail
], ],
) )
.mount( .mount(

View File

@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
import { Person, PersonList as PersonListProto } from "../items"; import { Person, PersonList as PersonListProto } from "../items";
import { apiFetch } from "./api"; import { apiFetch } from "./api";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { GameImage } from "./GameImage";
export function GameFilter() { export function GameFilter() {
const [people, setPeople] = useState<Person[]>([]); const [people, setPeople] = useState<Person[]>([]);
@ -37,10 +38,10 @@ export function GameFilter() {
selectedPersons.forEach((person) => { selectedPersons.forEach((person) => {
person.opinion.forEach((op) => { person.opinion.forEach((op) => {
if (op.wouldPlay) { if (!gameToPlayers.has(op.title)) {
if (!gameToPlayers.has(op.title)) { gameToPlayers.set(op.title, new Set());
gameToPlayers.set(op.title, new Set()); }
} if (!op.wouldPlay) {
gameToPlayers.get(op.title)!.add(person.name); gameToPlayers.get(op.title)!.add(person.name);
} }
}); });
@ -48,7 +49,7 @@ export function GameFilter() {
// Filter games where ALL selected people would play // Filter games where ALL selected people would play
const games = Array.from(gameToPlayers.entries()) const games = Array.from(gameToPlayers.entries())
.filter(([, players]) => players.size === selectedPeople.size) .filter(([, players]) => players.size === 0)
.map(([game]) => game); .map(([game]) => game);
setFilteredGames(games); setFilteredGames(games);
@ -137,6 +138,7 @@ export function GameFilter() {
> >
All {selectedPeople.size} selected would play All {selectedPeople.size} selected would play
</div> </div>
<GameImage game={game} />
</Link> </Link>
))} ))}
</ul> </ul>

View File

@ -0,0 +1,44 @@
import { useState } from "react";
interface GameImageProps {
game: string;
}
export function GameImage({ game }: GameImageProps) {
const [error, setError] = useState(false);
if (error) {
return (
<div
style={{
width: "100%",
height: "150px",
background: "#ddd",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "#666",
borderRadius: "8px",
marginTop: "0.5rem",
}}
>
No Image Available
</div>
);
}
return (
<img
src={`/api/game_thumbnail/${encodeURIComponent(game)}`}
alt={`${game} cover`}
onError={() => setError(true)}
style={{
width: "100%",
height: "auto",
borderRadius: "8px",
marginTop: "0.5rem",
objectFit: "cover",
}}
/>
);
}

View File

@ -6,65 +6,69 @@
"min_players": 1, "min_players": 1,
"max_players": 90, "max_players": 90,
"price": 0, "price": 0,
"remote_id": 0 "remote_id": 98626216952426
}, },
{ {
"title": "Test2", "title": " Asylum Life",
"source": 1, "source": 1,
"min_players": 1, "min_players": 1,
"max_players": 1, "max_players": 40,
"price": 0, "price": 0,
"remote_id": 0 "remote_id": 132352755769957
},
{
"title": "Factorio",
"source": 0,
"min_players": 1,
"max_players": 1000,
"price": 32,
"remote_id": 427520
} }
], ],
"users": [ "users": [
{ {
"person": { "person": {
"name": "John", "name": "HoherGeist",
"opinion": [ "opinion": [
{ {
"title": "Naramo Nuclear Plant V2", "title": "Naramo Nuclear Plant V2",
"would_play": true "would_play": true
},
{
"title": "Test2",
"would_play": true
} }
] ]
}, },
"password_hash": "$2b$12$DRvTP/ibTWULkuJJr285bumRd7SG3n5bYkDpb09Qpklqf6FeTiHkC" "password_hash": "$2b$12$v7w0E4NWRvxvfwM4rBmmnuG1MEDldRxA2TKejF8RtRgFvBayNxyoK"
}, },
{ {
"person": { "person": {
"name": "John2", "name": "Code002Lover",
"opinion": [ "opinion": [
{ {
"title": "Naramo Nuclear Plant V2", "title": " Asylum Life",
"would_play": false
},
{
"title": "Test2",
"would_play": true "would_play": true
} }
] ]
}, },
"password_hash": "$2b$12$DRvTP/ibTWULkuJJr285bumRd7SG3n5bYkDpb09Qpklqf6FeTiHkC" "password_hash": "$2b$12$4EVOAuLrrwFvtS8Z6/7n.ertig.agKLGdWF8flIO/LLB2Ir1A4oqW"
}, },
{ {
"person": { "person": {
"name": "John3", "name": "Cwaeck",
"opinion": [ "opinion": [
{ {
"title": "Naramo Nuclear Plant V2", "title": "Naramo Nuclear Plant V2",
"would_play": true
},
{
"title": " Asylum Life",
"would_play": false "would_play": false
}, },
{ {
"title": "Test2", "title": "Factorio",
"would_play": true "would_play": true
} }
] ]
}, },
"password_hash": "$2b$12$DRvTP/ibTWULkuJJr285bumRd7SG3n5bYkDpb09Qpklqf6FeTiHkC" "password_hash": "$2b$12$iezDgiYiMSEceyfDrriqVucQBx7v.U4TdKdc5qjEIJDbqsxiXHTBK"
} }
] ]
} }