merge `api` into `werewolves_proto`
This commit is contained in:
parent
ab1cb29952
commit
0fd0f8f5c9
|
|
@ -65,30 +65,6 @@ version = "1.0.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "api"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"argon2",
|
|
||||||
"async-trait",
|
|
||||||
"axum",
|
|
||||||
"axum-extra",
|
|
||||||
"bytes",
|
|
||||||
"chrono",
|
|
||||||
"ciborium",
|
|
||||||
"futures",
|
|
||||||
"leptos",
|
|
||||||
"log",
|
|
||||||
"rand 0.9.2",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sqlx",
|
|
||||||
"thiserror 2.0.17",
|
|
||||||
"uuid",
|
|
||||||
"werewolves-proto",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "argon2"
|
name = "argon2"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
@ -463,11 +439,11 @@ checksum = "2550f75b8cfac212855f6b1885455df8eaee8fe8e246b647d69146142e016084"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colored"
|
name = "colored"
|
||||||
version = "3.0.0"
|
version = "3.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
|
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4053,7 +4029,7 @@ name = "werewolves"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"api",
|
"argon2",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-extra",
|
"axum-extra",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -4102,14 +4078,24 @@ dependencies = [
|
||||||
name = "werewolves-proto"
|
name = "werewolves-proto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"argon2",
|
||||||
|
"async-trait",
|
||||||
|
"axum",
|
||||||
|
"axum-extra",
|
||||||
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"ciborium",
|
||||||
"colored",
|
"colored",
|
||||||
|
"futures",
|
||||||
|
"leptos",
|
||||||
"log",
|
"log",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sqlx",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"uuid",
|
"uuid",
|
||||||
"werewolves-macros",
|
"werewolves-macros",
|
||||||
|
|
@ -4202,15 +4188,6 @@ dependencies = [
|
||||||
"windows-targets 0.48.5",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.59.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.52.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.60.2"
|
version = "0.60.2"
|
||||||
|
|
@ -4244,22 +4221,6 @@ dependencies = [
|
||||||
"windows_x86_64_msvc 0.48.5",
|
"windows_x86_64_msvc 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
|
||||||
dependencies = [
|
|
||||||
"windows_aarch64_gnullvm 0.52.6",
|
|
||||||
"windows_aarch64_msvc 0.52.6",
|
|
||||||
"windows_i686_gnu 0.52.6",
|
|
||||||
"windows_i686_gnullvm 0.52.6",
|
|
||||||
"windows_i686_msvc 0.52.6",
|
|
||||||
"windows_x86_64_gnu 0.52.6",
|
|
||||||
"windows_x86_64_gnullvm 0.52.6",
|
|
||||||
"windows_x86_64_msvc 0.52.6",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.53.5"
|
version = "0.53.5"
|
||||||
|
|
@ -4270,7 +4231,7 @@ dependencies = [
|
||||||
"windows_aarch64_gnullvm 0.53.1",
|
"windows_aarch64_gnullvm 0.53.1",
|
||||||
"windows_aarch64_msvc 0.53.1",
|
"windows_aarch64_msvc 0.53.1",
|
||||||
"windows_i686_gnu 0.53.1",
|
"windows_i686_gnu 0.53.1",
|
||||||
"windows_i686_gnullvm 0.53.1",
|
"windows_i686_gnullvm",
|
||||||
"windows_i686_msvc 0.53.1",
|
"windows_i686_msvc 0.53.1",
|
||||||
"windows_x86_64_gnu 0.53.1",
|
"windows_x86_64_gnu 0.53.1",
|
||||||
"windows_x86_64_gnullvm 0.53.1",
|
"windows_x86_64_gnullvm 0.53.1",
|
||||||
|
|
@ -4283,12 +4244,6 @@ version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_gnullvm"
|
name = "windows_aarch64_gnullvm"
|
||||||
version = "0.53.1"
|
version = "0.53.1"
|
||||||
|
|
@ -4301,12 +4256,6 @@ version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.53.1"
|
version = "0.53.1"
|
||||||
|
|
@ -4319,24 +4268,12 @@ version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.53.1"
|
version = "0.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnullvm"
|
name = "windows_i686_gnullvm"
|
||||||
version = "0.53.1"
|
version = "0.53.1"
|
||||||
|
|
@ -4349,12 +4286,6 @@ version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.53.1"
|
version = "0.53.1"
|
||||||
|
|
@ -4367,12 +4298,6 @@ version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.53.1"
|
version = "0.53.1"
|
||||||
|
|
@ -4385,12 +4310,6 @@ version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.53.1"
|
version = "0.53.1"
|
||||||
|
|
@ -4403,12 +4322,6 @@ version = "0.48.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.52.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.53.1"
|
version = "0.53.1"
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ members = [
|
||||||
"werewolves-proto",
|
"werewolves-proto",
|
||||||
# "werewolves-server",
|
# "werewolves-server",
|
||||||
"werewolves",
|
"werewolves",
|
||||||
"api",
|
# "api",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[workspace.metadata.leptos]]
|
[[workspace.metadata.leptos]]
|
||||||
|
|
@ -120,8 +120,10 @@ serde = { version = "1.0.228" }
|
||||||
tokio = { version = "1.45.0", features = ["full"] }
|
tokio = { version = "1.45.0", features = ["full"] }
|
||||||
tower = { version = "0.5.2", features = ["full"] }
|
tower = { version = "0.5.2", features = ["full"] }
|
||||||
tower-http = { version = "0.6.4", features = ["full"] }
|
tower-http = { version = "0.6.4", features = ["full"] }
|
||||||
api = { path = "api" }
|
|
||||||
ciborium = { version = "0.2" }
|
ciborium = { version = "0.2" }
|
||||||
|
pretty_assertions = { version = "1.4" }
|
||||||
|
colored = { version = "3.1" }
|
||||||
|
pretty_env_logger = { version = "0.5" }
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 0
|
opt-level = 0
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "api"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bytes = { version = "1.10.1", features = ["serde"] }
|
|
||||||
rand = { version = "0.9", optional = true }
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
|
||||||
uuid = { workspace = true, features = ["serde", "v4"] }
|
|
||||||
thiserror = { workspace = true }
|
|
||||||
chrono = { workspace = true, features = ["serde"] }
|
|
||||||
sqlx = { workspace = true, optional = true }
|
|
||||||
axum = { workspace = true, optional = true, features = ["macros"] }
|
|
||||||
axum-extra = { workspace = true, optional = true, features = ["typed-header"] }
|
|
||||||
argon2 = { workspace = true, optional = true }
|
|
||||||
ciborium = { workspace = true }
|
|
||||||
async-trait = { workspace = true, optional = true }
|
|
||||||
werewolves-proto = { workspace = true }
|
|
||||||
serde_json = { workspace = true, optional = true }
|
|
||||||
futures = { workspace = true, optional = true }
|
|
||||||
|
|
||||||
log.workspace = true
|
|
||||||
leptos.workspace = true
|
|
||||||
anyhow.workspace = true
|
|
||||||
|
|
||||||
[features]
|
|
||||||
ssr = [
|
|
||||||
"dep:sqlx",
|
|
||||||
"dep:axum",
|
|
||||||
"dep:axum-extra",
|
|
||||||
"dep:argon2",
|
|
||||||
"dep:rand",
|
|
||||||
"dep:async-trait",
|
|
||||||
"dep:serde_json",
|
|
||||||
"dep:futures",
|
|
||||||
]
|
|
||||||
193
api/src/error.rs
193
api/src/error.rs
|
|
@ -1,193 +0,0 @@
|
||||||
use leptos::prelude::{ServerFnError, ServerFnErrorErr};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use thiserror::Error;
|
|
||||||
use werewolves_proto::error::GameError;
|
|
||||||
|
|
||||||
use crate::game::GameId;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)]
|
|
||||||
pub enum ServerError {
|
|
||||||
#[error("internal server error")]
|
|
||||||
InternalServerError,
|
|
||||||
#[error("not found")]
|
|
||||||
NotFound,
|
|
||||||
#[error("user already exists")]
|
|
||||||
UserAlreadyExists,
|
|
||||||
#[error("invalid credentials")]
|
|
||||||
InvalidCredentials,
|
|
||||||
#[error("token expired")]
|
|
||||||
ExpiredToken,
|
|
||||||
#[error("connection error")]
|
|
||||||
ConnectionError,
|
|
||||||
#[error("invalid request: {0}")]
|
|
||||||
InvalidRequest(String),
|
|
||||||
#[error("you're already in an active game: {0}")]
|
|
||||||
AlreadyInActiveGame(GameId),
|
|
||||||
#[error("{0}")]
|
|
||||||
GameError(#[from] GameError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl leptos::prelude::FromServerFnError for ServerError {
|
|
||||||
type Encoder = leptos::server_fn::codec::JsonEncoding;
|
|
||||||
|
|
||||||
fn from_server_fn_error(value: ServerFnErrorErr) -> Self {
|
|
||||||
match value {
|
|
||||||
ServerFnErrorErr::ServerError(err) => {
|
|
||||||
log::error!("server error: {err}; truncating to ServerError::InternalServerError");
|
|
||||||
ServerError::InternalServerError
|
|
||||||
}
|
|
||||||
ServerFnErrorErr::MiddlewareError(err) => {
|
|
||||||
log::error!(
|
|
||||||
"middleware error: {err}; truncating to ServerError::InternalServerError"
|
|
||||||
);
|
|
||||||
ServerError::InternalServerError
|
|
||||||
}
|
|
||||||
ServerFnErrorErr::Request(err) => {
|
|
||||||
const CONN_ERR: &str = "TypeError: NetworkError when attempting to fetch resource.";
|
|
||||||
if err == CONN_ERR {
|
|
||||||
Self::ConnectionError
|
|
||||||
} else {
|
|
||||||
Self::InvalidRequest(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err => {
|
|
||||||
let t = match &err {
|
|
||||||
ServerFnErrorErr::Registration(_) => "Registration",
|
|
||||||
ServerFnErrorErr::UnsupportedRequestMethod(_) => "UnsupportedRequestMethod",
|
|
||||||
ServerFnErrorErr::Request(_) => "Request",
|
|
||||||
ServerFnErrorErr::ServerError(_) => "ServerError",
|
|
||||||
ServerFnErrorErr::MiddlewareError(_) => "MiddlewareError",
|
|
||||||
ServerFnErrorErr::Deserialization(_) => "Deserialization",
|
|
||||||
ServerFnErrorErr::Serialization(_) => "Serialization",
|
|
||||||
ServerFnErrorErr::Args(_) => "Args",
|
|
||||||
ServerFnErrorErr::MissingArg(_) => "MissingArg",
|
|
||||||
ServerFnErrorErr::Response(_) => "Response",
|
|
||||||
};
|
|
||||||
Self::InvalidRequest(format!("[{t}]: {err}"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// impl core::str::FromStr for ServerError {
|
|
||||||
// type Err = core::convert::Infallible;
|
|
||||||
|
|
||||||
// fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
// panic!("ServerError::FromStr({s})")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// impl From<ServerFnError> for ServerError {
|
|
||||||
// fn from(err: ServerFnError) -> Self {
|
|
||||||
// match err {
|
|
||||||
// ServerFnError::ServerError(err) => {
|
|
||||||
// log::error!("server error: {err}; truncating to ServerError::InternalServerError");
|
|
||||||
// ServerError::InternalServerError
|
|
||||||
// }
|
|
||||||
// ServerFnError::MiddlewareError(err) => {
|
|
||||||
// log::error!(
|
|
||||||
// "middleware error: {err}; truncating to ServerError::InternalServerError"
|
|
||||||
// );
|
|
||||||
// ServerError::InternalServerError
|
|
||||||
// }
|
|
||||||
// ServerFnError::Request(err) => {
|
|
||||||
// log::error!("[{err}]");
|
|
||||||
// Self::ConnectionError
|
|
||||||
// }
|
|
||||||
// err => {
|
|
||||||
// let t = match &err {
|
|
||||||
// ServerFnError::Registration(_) => "Registration",
|
|
||||||
// ServerFnError::Request(_) => "Request",
|
|
||||||
// ServerFnError::ServerError(_) => "ServerError",
|
|
||||||
// ServerFnError::MiddlewareError(_) => "MiddlewareError",
|
|
||||||
// ServerFnError::Deserialization(_) => "Deserialization",
|
|
||||||
// ServerFnError::Serialization(_) => "Serialization",
|
|
||||||
// ServerFnError::Args(_) => "Args",
|
|
||||||
// ServerFnError::MissingArg(_) => "MissingArg",
|
|
||||||
// ServerFnError::Response(_) => "Response",
|
|
||||||
// ServerFnError::WrappedServerError(_) => "WrappedServerError",
|
|
||||||
// };
|
|
||||||
// Self::InvalidRequest(format!("[{t}]: {err}"))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
impl From<DatabaseError> for ServerError {
|
|
||||||
fn from(err: DatabaseError) -> Self {
|
|
||||||
match err {
|
|
||||||
DatabaseError::NotFound => ServerError::NotFound,
|
|
||||||
DatabaseError::UserAlreadyExists => ServerError::UserAlreadyExists,
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
_ => {
|
|
||||||
log::error!(
|
|
||||||
"converting database error into ServerError::InternalServerError: {err}"
|
|
||||||
);
|
|
||||||
ServerError::InternalServerError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Error)]
|
|
||||||
pub enum DatabaseError {
|
|
||||||
#[error("user already exists")]
|
|
||||||
UserAlreadyExists,
|
|
||||||
#[error("password hashing error: {0}")]
|
|
||||||
PasswordHashError(String),
|
|
||||||
#[error("sqlx error: {0}")]
|
|
||||||
SqlxError(String),
|
|
||||||
#[error("not found")]
|
|
||||||
NotFound,
|
|
||||||
#[error("(de)serialization error: {0}")]
|
|
||||||
Serialization(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
impl From<serde_json::Error> for DatabaseError {
|
|
||||||
fn from(value: serde_json::Error) -> Self {
|
|
||||||
Self::Serialization(value.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
impl axum::response::IntoResponse for ServerError {
|
|
||||||
fn into_response(self) -> axum::response::Response {
|
|
||||||
use axum::{Json, http::StatusCode};
|
|
||||||
|
|
||||||
use crate::cbor::Cbor;
|
|
||||||
|
|
||||||
(
|
|
||||||
match self {
|
|
||||||
ServerError::AlreadyInActiveGame(_)
|
|
||||||
| ServerError::GameError(_)
|
|
||||||
| ServerError::InvalidCredentials
|
|
||||||
| ServerError::InvalidRequest(_)
|
|
||||||
| ServerError::UserAlreadyExists => StatusCode::BAD_REQUEST,
|
|
||||||
ServerError::NotFound => StatusCode::NOT_FOUND,
|
|
||||||
ServerError::ConnectionError | ServerError::InternalServerError => {
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
}
|
|
||||||
ServerError::ExpiredToken => StatusCode::UNAUTHORIZED,
|
|
||||||
},
|
|
||||||
Json(self),
|
|
||||||
)
|
|
||||||
.into_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
impl From<sqlx::Error> for DatabaseError {
|
|
||||||
fn from(err: sqlx::Error) -> Self {
|
|
||||||
match err {
|
|
||||||
sqlx::Error::RowNotFound => Self::NotFound,
|
|
||||||
_ => Self::SqlxError(err.to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
impl From<argon2::password_hash::Error> for DatabaseError {
|
|
||||||
fn from(err: argon2::password_hash::Error) -> Self {
|
|
||||||
Self::PasswordHashError(err.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use werewolves_proto::{character::CharacterId, message::PublicIdentity, player::PlayerId};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct PlayerIdentity {
|
|
||||||
pub player_id: PlayerId,
|
|
||||||
pub character_id: CharacterId,
|
|
||||||
pub public: PublicIdentity,
|
|
||||||
}
|
|
||||||
111
api/src/lib.rs
111
api/src/lib.rs
|
|
@ -1,111 +0,0 @@
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
pub mod cbor;
|
|
||||||
pub mod cbor_leptos;
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
pub mod db;
|
|
||||||
pub mod error;
|
|
||||||
pub mod limited;
|
|
||||||
// pub mod routes;
|
|
||||||
pub mod game;
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
pub mod identity;
|
|
||||||
pub mod message;
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
pub mod state;
|
|
||||||
pub mod token;
|
|
||||||
pub mod user;
|
|
||||||
|
|
||||||
pub type ServerResult<T> = core::result::Result<T, error::ServerError>;
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! id_impl {
|
|
||||||
($name:ident) => {
|
|
||||||
#[derive(
|
|
||||||
Debug, Clone, Copy, PartialEq, Eq, Hash, ::serde::Serialize, ::serde::Deserialize,
|
|
||||||
)]
|
|
||||||
pub struct $name(uuid::Uuid);
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
impl sqlx::TypeInfo for $name {
|
|
||||||
fn is_null(&self) -> bool {
|
|
||||||
self.0 == uuid::Uuid::nil()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
"uuid"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
impl sqlx::Type<sqlx::Postgres> for $name {
|
|
||||||
fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
|
|
||||||
<uuid::Uuid as sqlx::Type<sqlx::Postgres>>::type_info()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for $name {
|
|
||||||
fn encode_by_ref(
|
|
||||||
&self,
|
|
||||||
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
|
|
||||||
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
|
||||||
self.0.encode_by_ref(buf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
impl<'r> sqlx::Decode<'r, sqlx::Postgres> for $name {
|
|
||||||
fn decode(
|
|
||||||
value: <sqlx::Postgres as sqlx::Database>::ValueRef<'r>,
|
|
||||||
) -> Result<Self, sqlx::error::BoxDynError> {
|
|
||||||
Ok(Self(uuid::Uuid::decode(value)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<uuid::Uuid> for $name {
|
|
||||||
fn from(value: uuid::Uuid) -> Self {
|
|
||||||
Self::from_uuid(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<$name> for uuid::Uuid {
|
|
||||||
fn from(value: $name) -> Self {
|
|
||||||
value.into_uuid()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for $name {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl $name {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(uuid::Uuid::new_v4())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn from_uuid(uuid: uuid::Uuid) -> Self {
|
|
||||||
Self(uuid)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn into_uuid(self) -> uuid::Uuid {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::fmt::Display for $name {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::str::FromStr for $name {
|
|
||||||
type Err = uuid::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
Ok(Self(uuid::Uuid::from_str(s)?))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use werewolves_proto::message::{
|
|
||||||
ClientMessage, ServerToClientMessage,
|
|
||||||
host::{HostMessage, ServerToHostMessage},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::token::TokenString;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum WrappedServerMessage {
|
|
||||||
Authentication(TokenString),
|
|
||||||
HostMessage(HostMessage),
|
|
||||||
ClientMessage(ClientMessage),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<TokenString> for WrappedServerMessage {
|
|
||||||
fn from(value: TokenString) -> Self {
|
|
||||||
Self::Authentication(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<HostMessage> for WrappedServerMessage {
|
|
||||||
fn from(value: HostMessage) -> Self {
|
|
||||||
Self::HostMessage(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ClientMessage> for WrappedServerMessage {
|
|
||||||
fn from(value: ClientMessage) -> Self {
|
|
||||||
Self::ClientMessage(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum IntoClientResponse {
|
|
||||||
Player(ServerToClientMessage),
|
|
||||||
Host(ServerToHostMessage),
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum Method {
|
|
||||||
Options,
|
|
||||||
Get,
|
|
||||||
Post,
|
|
||||||
Put,
|
|
||||||
Delete,
|
|
||||||
Head,
|
|
||||||
Trace,
|
|
||||||
Connect,
|
|
||||||
Patch,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Route {
|
|
||||||
pub method: Method,
|
|
||||||
pub path: &'static str,
|
|
||||||
pub authenticated: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Route {
|
|
||||||
const fn new(method: Method, path: &'static str, authenticated: bool) -> Self {
|
|
||||||
Self {
|
|
||||||
method,
|
|
||||||
path,
|
|
||||||
authenticated,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Routes {
|
|
||||||
pub create_user: Route,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const ROUTES: Routes = Routes {
|
|
||||||
create_user: Route::new(Method::Post, "/api/users", false),
|
|
||||||
};
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
use std::{collections::HashMap, sync::Arc};
|
|
||||||
|
|
||||||
use futures::lock::Mutex;
|
|
||||||
use leptos::config::LeptosOptions;
|
|
||||||
|
|
||||||
use crate::{db::Database, game::GameId};
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "ssr", derive(axum::extract::FromRef))]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct AppState {
|
|
||||||
pub db: Database,
|
|
||||||
pub leptos_options: LeptosOptions,
|
|
||||||
}
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use werewolves_proto::player::PlayerId;
|
|
||||||
|
|
||||||
use crate::{limited::ClampedString, token::TokenString};
|
|
||||||
|
|
||||||
pub type Username = ClampedString<1, 0x40>;
|
|
||||||
pub type Password = ClampedString<6, 0x100>;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
|
||||||
pub struct UserLogin {
|
|
||||||
pub username: Username,
|
|
||||||
pub password: Password,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
|
||||||
pub struct ChangePassword {
|
|
||||||
pub current: Password,
|
|
||||||
pub new: Password,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
|
||||||
pub struct DeleteUserRequest {
|
|
||||||
pub password: Password,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "ssr", derive(::sqlx::FromRow))]
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct Session {
|
|
||||||
pub username: String,
|
|
||||||
pub display_name: Option<String>,
|
|
||||||
pub pronouns: Option<String>,
|
|
||||||
|
|
||||||
pub user_created_at: DateTime<Utc>,
|
|
||||||
pub user_updated_at: DateTime<Utc>,
|
|
||||||
pub token_created_at: DateTime<Utc>,
|
|
||||||
pub token_expires_at: DateTime<Utc>,
|
|
||||||
pub token: TokenString,
|
|
||||||
}
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct ProfileUpdate {
|
|
||||||
pub display_name: Option<String>,
|
|
||||||
pub pronouns: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
crate::id_impl!(UserId);
|
|
||||||
|
|
||||||
impl From<PlayerId> for UserId {
|
|
||||||
fn from(value: PlayerId) -> Self {
|
|
||||||
UserId(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<UserId> for PlayerId {
|
|
||||||
fn from(value: UserId) -> Self {
|
|
||||||
PlayerId::from_uuid(value.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,16 +4,38 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { version = "2" }
|
bytes = { version = "1.10.1", features = ["serde"] }
|
||||||
log = { version = "0.4" }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { version = "1.0" }
|
uuid = { workspace = true, features = ["serde", "v4"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
thiserror = { workspace = true }
|
||||||
uuid = { version = "1.17", features = ["v4", "serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
rand = { version = "0.9", features = ["std_rng"] }
|
sqlx = { workspace = true, optional = true }
|
||||||
werewolves-macros = { path = "../werewolves-macros" }
|
axum = { workspace = true, optional = true, features = ["macros"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
axum-extra = { workspace = true, optional = true, features = ["typed-header"] }
|
||||||
|
argon2 = { workspace = true, optional = true }
|
||||||
|
ciborium = { workspace = true }
|
||||||
|
async-trait = { workspace = true, optional = true }
|
||||||
|
serde_json = { workspace = true, optional = true }
|
||||||
|
futures = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
log.workspace = true
|
||||||
|
leptos.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
werewolves-macros.workspace = true
|
||||||
|
rand.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = { version = "1" }
|
colored.workspace = true
|
||||||
pretty_env_logger = { version = "0.5" }
|
pretty_assertions.workspace = true
|
||||||
colored = { version = "3.0" }
|
pretty_env_logger.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
ssr = [
|
||||||
|
"dep:sqlx",
|
||||||
|
"dep:axum",
|
||||||
|
"dep:axum-extra",
|
||||||
|
"dep:argon2",
|
||||||
|
"dep:async-trait",
|
||||||
|
"dep:serde_json",
|
||||||
|
"dep:futures",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,6 @@ use axum::{
|
||||||
use axum_extra::headers::Mime;
|
use axum_extra::headers::Mime;
|
||||||
use bytes::{BufMut, BytesMut};
|
use bytes::{BufMut, BytesMut};
|
||||||
use core::fmt::Display;
|
use core::fmt::Display;
|
||||||
use leptos::server_fn::{
|
|
||||||
ContentType, Decodes, Encodes, Format, FormatType,
|
|
||||||
codec::{Post, Put},
|
|
||||||
};
|
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
|
|
||||||
const CBOR_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("application/cbor");
|
const CBOR_CONTENT_TYPE: HeaderValue = HeaderValue::from_static("application/cbor");
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
use core::marker::PhantomData;
|
|
||||||
use std::io::Read;
|
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use leptos::{
|
use leptos::{
|
||||||
server::codee::{Decoder, Encoder},
|
server::codee::{Decoder, Encoder},
|
||||||
|
|
@ -26,6 +26,7 @@ use crate::{
|
||||||
diedto::DiedTo,
|
diedto::DiedTo,
|
||||||
error::GameError,
|
error::GameError,
|
||||||
game::{GameTime, Village, night::changes::NightChange},
|
game::{GameTime, Village, night::changes::NightChange},
|
||||||
|
id_impl,
|
||||||
message::{CharacterIdentity, Identification, PublicIdentity, night::ActionPrompt},
|
message::{CharacterIdentity, Identification, PublicIdentity, night::ActionPrompt},
|
||||||
player::{PlayerId, RoleChange},
|
player::{PlayerId, RoleChange},
|
||||||
role::{
|
role::{
|
||||||
|
|
@ -37,29 +38,7 @@ use crate::{
|
||||||
|
|
||||||
type Result<T> = core::result::Result<T, GameError>;
|
type Result<T> = core::result::Result<T, GameError>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
id_impl!(CharacterId);
|
||||||
pub struct CharacterId(uuid::Uuid);
|
|
||||||
|
|
||||||
impl CharacterId {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(uuid::Uuid::new_v4())
|
|
||||||
}
|
|
||||||
pub const fn from_u128(v: u128) -> Self {
|
|
||||||
Self(uuid::Uuid::from_u128(v))
|
|
||||||
}
|
|
||||||
pub const fn from_uuid(v: uuid::Uuid) -> Self {
|
|
||||||
Self(v)
|
|
||||||
}
|
|
||||||
pub const fn into_uuid(self) -> uuid::Uuid {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for CharacterId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Character {
|
pub struct Character {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,13 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{game::GameTime, message::PublicIdentity, player::PlayerId, role::RoleTitle};
|
use crate::{
|
||||||
|
game::{GameId, GameTime},
|
||||||
|
message::PublicIdentity,
|
||||||
|
player::PlayerId,
|
||||||
|
role::RoleTitle,
|
||||||
|
};
|
||||||
|
use leptos::prelude::ServerFnErrorErr;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)]
|
||||||
pub enum GameError {
|
pub enum GameError {
|
||||||
|
|
@ -121,4 +127,149 @@ pub enum GameError {
|
||||||
CannotJoinStartedGame,
|
CannotJoinStartedGame,
|
||||||
#[error("game already started")]
|
#[error("game already started")]
|
||||||
GameAlreadyStarted,
|
GameAlreadyStarted,
|
||||||
|
#[error("you're already in another game: {0}")]
|
||||||
|
AlreadyInAnotherGame(GameId),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Error, Serialize, Deserialize)]
|
||||||
|
pub enum ServerError {
|
||||||
|
#[error("internal server error")]
|
||||||
|
InternalServerError,
|
||||||
|
#[error("not found")]
|
||||||
|
NotFound,
|
||||||
|
#[error("user already exists")]
|
||||||
|
UserAlreadyExists,
|
||||||
|
#[error("invalid credentials")]
|
||||||
|
InvalidCredentials,
|
||||||
|
#[error("token expired")]
|
||||||
|
ExpiredToken,
|
||||||
|
#[error("connection error")]
|
||||||
|
ConnectionError,
|
||||||
|
#[error("invalid request: {0}")]
|
||||||
|
InvalidRequest(String),
|
||||||
|
#[error("you're already in an active game: {0}")]
|
||||||
|
AlreadyInActiveGame(GameId),
|
||||||
|
#[error("{0}")]
|
||||||
|
GameError(#[from] GameError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl leptos::prelude::FromServerFnError for ServerError {
|
||||||
|
type Encoder = leptos::server_fn::codec::JsonEncoding;
|
||||||
|
|
||||||
|
fn from_server_fn_error(value: ServerFnErrorErr) -> Self {
|
||||||
|
match value {
|
||||||
|
ServerFnErrorErr::ServerError(err) => {
|
||||||
|
log::error!("server error: {err}; truncating to ServerError::InternalServerError");
|
||||||
|
ServerError::InternalServerError
|
||||||
|
}
|
||||||
|
ServerFnErrorErr::MiddlewareError(err) => {
|
||||||
|
log::error!(
|
||||||
|
"middleware error: {err}; truncating to ServerError::InternalServerError"
|
||||||
|
);
|
||||||
|
ServerError::InternalServerError
|
||||||
|
}
|
||||||
|
ServerFnErrorErr::Request(err) => {
|
||||||
|
const CONN_ERR: &str = "TypeError: NetworkError when attempting to fetch resource.";
|
||||||
|
if err == CONN_ERR {
|
||||||
|
Self::ConnectionError
|
||||||
|
} else {
|
||||||
|
Self::InvalidRequest(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err => {
|
||||||
|
let t = match &err {
|
||||||
|
ServerFnErrorErr::Registration(_) => "Registration",
|
||||||
|
ServerFnErrorErr::UnsupportedRequestMethod(_) => "UnsupportedRequestMethod",
|
||||||
|
ServerFnErrorErr::Request(_) => "Request",
|
||||||
|
ServerFnErrorErr::ServerError(_) => "ServerError",
|
||||||
|
ServerFnErrorErr::MiddlewareError(_) => "MiddlewareError",
|
||||||
|
ServerFnErrorErr::Deserialization(_) => "Deserialization",
|
||||||
|
ServerFnErrorErr::Serialization(_) => "Serialization",
|
||||||
|
ServerFnErrorErr::Args(_) => "Args",
|
||||||
|
ServerFnErrorErr::MissingArg(_) => "MissingArg",
|
||||||
|
ServerFnErrorErr::Response(_) => "Response",
|
||||||
|
};
|
||||||
|
Self::InvalidRequest(format!("[{t}]: {err}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DatabaseError> for ServerError {
|
||||||
|
fn from(err: DatabaseError) -> Self {
|
||||||
|
match err {
|
||||||
|
DatabaseError::NotFound => ServerError::NotFound,
|
||||||
|
DatabaseError::UserAlreadyExists => ServerError::UserAlreadyExists,
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
_ => {
|
||||||
|
log::error!(
|
||||||
|
"converting database error into ServerError::InternalServerError: {err}"
|
||||||
|
);
|
||||||
|
ServerError::InternalServerError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Error)]
|
||||||
|
pub enum DatabaseError {
|
||||||
|
#[error("user already exists")]
|
||||||
|
UserAlreadyExists,
|
||||||
|
#[error("password hashing error: {0}")]
|
||||||
|
PasswordHashError(String),
|
||||||
|
#[error("sqlx error: {0}")]
|
||||||
|
SqlxError(String),
|
||||||
|
#[error("not found")]
|
||||||
|
NotFound,
|
||||||
|
#[error("(de)serialization error: {0}")]
|
||||||
|
Serialization(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<leptos::serde_json::Error> for DatabaseError {
|
||||||
|
fn from(value: leptos::serde_json::Error) -> Self {
|
||||||
|
Self::Serialization(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl axum::response::IntoResponse for ServerError {
|
||||||
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
use axum::{Json, http::StatusCode};
|
||||||
|
|
||||||
|
use crate::cbor::Cbor;
|
||||||
|
|
||||||
|
(
|
||||||
|
match self {
|
||||||
|
ServerError::AlreadyInActiveGame(_)
|
||||||
|
| ServerError::GameError(_)
|
||||||
|
| ServerError::InvalidCredentials
|
||||||
|
| ServerError::InvalidRequest(_)
|
||||||
|
| ServerError::UserAlreadyExists => StatusCode::BAD_REQUEST,
|
||||||
|
ServerError::NotFound => StatusCode::NOT_FOUND,
|
||||||
|
ServerError::ConnectionError | ServerError::InternalServerError => {
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
}
|
||||||
|
ServerError::ExpiredToken => StatusCode::UNAUTHORIZED,
|
||||||
|
},
|
||||||
|
Json(self),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl From<sqlx::Error> for DatabaseError {
|
||||||
|
fn from(err: sqlx::Error) -> Self {
|
||||||
|
match err {
|
||||||
|
sqlx::Error::RowNotFound => Self::NotFound,
|
||||||
|
_ => Self::SqlxError(err.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl From<argon2::password_hash::Error> for DatabaseError {
|
||||||
|
fn from(err: argon2::password_hash::Error) -> Self {
|
||||||
|
Self::PasswordHashError(err.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ use crate::{
|
||||||
night::{Night, ServerAction},
|
night::{Night, ServerAction},
|
||||||
story::{DayDetail, GameActions, GameStory, NightDetails},
|
story::{DayDetail, GameActions, GameStory, NightDetails},
|
||||||
},
|
},
|
||||||
|
id_impl,
|
||||||
message::{
|
message::{
|
||||||
CharacterState, ClientDeadChat, Identification, ServerToClientMessage,
|
CharacterState, ClientDeadChat, Identification, ServerToClientMessage,
|
||||||
dead::{DeadChatContent, DeadChatMessage},
|
dead::{DeadChatContent, DeadChatMessage},
|
||||||
|
|
@ -53,6 +54,8 @@ pub use {
|
||||||
|
|
||||||
type Result<T> = core::result::Result<T, GameError>;
|
type Result<T> = core::result::Result<T, GameError>;
|
||||||
|
|
||||||
|
id_impl!(GameId);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
started: DateTime<Utc>,
|
started: DateTime<Utc>,
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
|
use crate::{
|
||||||
|
game::{Game, GameSettings, story::GameStory},
|
||||||
|
player::PlayerId,
|
||||||
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use werewolves_proto::game::{Game, GameSettings, story::GameStory};
|
|
||||||
|
|
||||||
use crate::{id_impl, user::UserId};
|
pub use crate::game::GameId;
|
||||||
|
|
||||||
id_impl!(GameId);
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "ssr", derive(::sqlx::FromRow))]
|
#[cfg_attr(feature = "ssr", derive(::sqlx::FromRow))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GameRecord {
|
pub struct GameRecord {
|
||||||
pub id: GameId,
|
pub id: GameId,
|
||||||
pub host: UserId,
|
pub host: PlayerId,
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub game_state: GameRecordState,
|
pub game_state: GameRecordState,
|
||||||
}
|
}
|
||||||
|
|
@ -15,14 +15,121 @@
|
||||||
#![allow(clippy::new_without_default)]
|
#![allow(clippy::new_without_default)]
|
||||||
pub mod aura;
|
pub mod aura;
|
||||||
pub mod bag;
|
pub mod bag;
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub mod cbor;
|
||||||
|
pub mod cbor_leptos;
|
||||||
pub mod character;
|
pub mod character;
|
||||||
pub mod diedto;
|
pub mod diedto;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod game;
|
pub mod game;
|
||||||
|
pub mod game_record;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod game_test;
|
mod game_test;
|
||||||
|
pub mod limited;
|
||||||
|
mod log;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod nonzero;
|
pub mod nonzero;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod role;
|
pub mod role;
|
||||||
pub mod team;
|
pub mod team;
|
||||||
|
pub mod token;
|
||||||
|
pub use log::*;
|
||||||
|
|
||||||
|
pub type ServerResult<T> = core::result::Result<T, error::ServerError>;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! id_impl {
|
||||||
|
($name:ident) => {
|
||||||
|
#[derive(
|
||||||
|
Debug, Clone, Copy, PartialEq, Eq, Hash, ::serde::Serialize, ::serde::Deserialize,
|
||||||
|
)]
|
||||||
|
pub struct $name(uuid::Uuid);
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl sqlx::TypeInfo for $name {
|
||||||
|
fn is_null(&self) -> bool {
|
||||||
|
self.0 == uuid::Uuid::nil()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"uuid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl sqlx::Type<sqlx::Postgres> for $name {
|
||||||
|
fn type_info() -> <sqlx::Postgres as sqlx::Database>::TypeInfo {
|
||||||
|
<uuid::Uuid as sqlx::Type<sqlx::Postgres>>::type_info()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for $name {
|
||||||
|
fn encode_by_ref(
|
||||||
|
&self,
|
||||||
|
buf: &mut <sqlx::Postgres as sqlx::Database>::ArgumentBuffer<'q>,
|
||||||
|
) -> core::result::Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
|
||||||
|
self.0.encode_by_ref(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl<'r> sqlx::Decode<'r, sqlx::Postgres> for $name {
|
||||||
|
fn decode(
|
||||||
|
value: <sqlx::Postgres as sqlx::Database>::ValueRef<'r>,
|
||||||
|
) -> core::result::Result<Self, sqlx::error::BoxDynError> {
|
||||||
|
Ok(Self(uuid::Uuid::decode(value)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<uuid::Uuid> for $name {
|
||||||
|
fn from(value: uuid::Uuid) -> Self {
|
||||||
|
Self::from_uuid(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<$name> for uuid::Uuid {
|
||||||
|
fn from(value: $name) -> Self {
|
||||||
|
value.into_uuid()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for $name {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl $name {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(uuid::Uuid::new_v4())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn from_uuid(uuid: uuid::Uuid) -> Self {
|
||||||
|
Self(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn into_uuid(self) -> uuid::Uuid {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn from_u128(u: u128) -> Self {
|
||||||
|
Self(::uuid::Uuid::from_u128(u))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for $name {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
core::fmt::Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::str::FromStr for $name {
|
||||||
|
type Err = uuid::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
|
||||||
|
Ok(Self(uuid::Uuid::from_str(s)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
pub trait LogError {
|
||||||
|
fn log(self, loc: CodePath, level: log::Level);
|
||||||
|
fn log_warn(self, loc: CodePath);
|
||||||
|
fn log_err(self, loc: CodePath);
|
||||||
|
fn log_debug(self, loc: CodePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CodePath {
|
||||||
|
pub module_path: &'static str,
|
||||||
|
pub loc: &'static std::panic::Location<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! loc {
|
||||||
|
() => {
|
||||||
|
(::werewolves_proto::CodePath {
|
||||||
|
module_path: log::__private_api::module_path!(),
|
||||||
|
loc: log::__private_api::loc(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, E> LogError for Result<T, E>
|
||||||
|
where
|
||||||
|
E: core::fmt::Display,
|
||||||
|
{
|
||||||
|
fn log(self, loc: CodePath, lvl: log::Level) {
|
||||||
|
let Err(err) = self else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if lvl <= log::STATIC_MAX_LEVEL && lvl <= log::max_level() {
|
||||||
|
log::__private_api::log(
|
||||||
|
log::__log_logger!(__log_global_logger),
|
||||||
|
log::__private_api::format_args!("{err}"),
|
||||||
|
lvl,
|
||||||
|
&(loc.module_path, loc.module_path, loc.loc),
|
||||||
|
(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_warn(self, loc: CodePath) {
|
||||||
|
if self.is_err() {
|
||||||
|
self.log(loc, log::Level::Warn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_err(self, loc: CodePath) {
|
||||||
|
if self.is_err() {
|
||||||
|
self.log(loc, log::Level::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_debug(self, loc: CodePath) {
|
||||||
|
if self.is_err() {
|
||||||
|
self.log(loc, log::Level::Debug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,10 @@ pub mod host;
|
||||||
mod ident;
|
mod ident;
|
||||||
pub mod night;
|
pub mod night;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
message::host::{HostMessage, ServerToHostMessage},
|
||||||
|
token::TokenString,
|
||||||
|
};
|
||||||
use core::num::NonZeroU8;
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
@ -25,11 +29,8 @@ use serde::{Deserialize, Serialize};
|
||||||
use werewolves_macros::Titles;
|
use werewolves_macros::Titles;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
character::CharacterId,
|
character::CharacterId, error::GameError, game::story::GameStory,
|
||||||
error::GameError,
|
message::dead::DeadChatMessage, role::RoleTitle,
|
||||||
game::{GameOver, story::GameStory},
|
|
||||||
message::dead::DeadChatMessage,
|
|
||||||
role::RoleTitle,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
|
@ -88,3 +89,34 @@ pub enum ServerToClientMessage {
|
||||||
pub enum PlayerUpdate {
|
pub enum PlayerUpdate {
|
||||||
Number(NonZeroU8),
|
Number(NonZeroU8),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum WrappedServerMessage {
|
||||||
|
Authentication(TokenString),
|
||||||
|
HostMessage(HostMessage),
|
||||||
|
ClientMessage(ClientMessage),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TokenString> for WrappedServerMessage {
|
||||||
|
fn from(value: TokenString) -> Self {
|
||||||
|
Self::Authentication(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HostMessage> for WrappedServerMessage {
|
||||||
|
fn from(value: HostMessage) -> Self {
|
||||||
|
Self::HostMessage(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ClientMessage> for WrappedServerMessage {
|
||||||
|
fn from(value: ClientMessage) -> Self {
|
||||||
|
Self::ClientMessage(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum IntoClientResponse {
|
||||||
|
Player(ServerToClientMessage),
|
||||||
|
Host(ServerToHostMessage),
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,41 +12,20 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
use core::fmt::Display;
|
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
|
id_impl,
|
||||||
|
limited::ClampedString,
|
||||||
|
message::PublicIdentity,
|
||||||
role::{Role, RoleTitle},
|
role::{Role, RoleTitle},
|
||||||
|
token::TokenString,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
id_impl!(PlayerId);
|
||||||
pub struct PlayerId(uuid::Uuid);
|
|
||||||
|
|
||||||
impl PlayerId {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(uuid::Uuid::new_v4())
|
|
||||||
}
|
|
||||||
pub const fn from_u128(v: u128) -> Self {
|
|
||||||
Self(uuid::Uuid::from_u128(v))
|
|
||||||
}
|
|
||||||
pub const fn from_uuid(uuid: uuid::Uuid) -> Self {
|
|
||||||
Self(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PlayerId> for uuid::Uuid {
|
|
||||||
fn from(value: PlayerId) -> Self {
|
|
||||||
value.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for PlayerId {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
self.0.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Player {
|
pub struct Player {
|
||||||
|
|
@ -82,3 +61,48 @@ pub struct RoleChange {
|
||||||
pub new_role: RoleTitle,
|
pub new_role: RoleTitle,
|
||||||
pub changed_on_night: u8,
|
pub changed_on_night: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Username = ClampedString<1, 0x40>;
|
||||||
|
pub type Password = ClampedString<6, 0x100>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct UserLogin {
|
||||||
|
pub username: Username,
|
||||||
|
pub password: Password,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct ChangePassword {
|
||||||
|
pub current: Password,
|
||||||
|
pub new: Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
|
pub struct DeleteUserRequest {
|
||||||
|
pub password: Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "ssr", derive(::sqlx::FromRow))]
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Session {
|
||||||
|
pub username: String,
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
pub pronouns: Option<String>,
|
||||||
|
|
||||||
|
pub user_created_at: DateTime<Utc>,
|
||||||
|
pub user_updated_at: DateTime<Utc>,
|
||||||
|
pub token_created_at: DateTime<Utc>,
|
||||||
|
pub token_expires_at: DateTime<Utc>,
|
||||||
|
pub token: TokenString,
|
||||||
|
}
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ProfileUpdate {
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
pub pronouns: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct PlayerIdentity {
|
||||||
|
pub player_id: PlayerId,
|
||||||
|
pub character_id: CharacterId,
|
||||||
|
pub public: PublicIdentity,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,19 @@
|
||||||
use crate::{limited::FixedLenString, user::Username};
|
// Copyright (C) 2026 Emilis Bliūdžius
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::{limited::FixedLenString, player::Username};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -16,8 +16,8 @@ leptos_meta = { workspace = true }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
||||||
wasm-bindgen = { workspace = true, optional = true }
|
wasm-bindgen = { workspace = true, optional = true }
|
||||||
getrandom = { version = "=0.3.4", optional = true }
|
getrandom = { version = "=0.3.4", optional = true }
|
||||||
colored = { version = "3.0", optional = true }
|
colored = { workspace = true, optional = true }
|
||||||
pretty_env_logger = { version = "0.5", optional = true }
|
pretty_env_logger = { workspace = true, optional = true }
|
||||||
sqlx = { workspace = true, optional = true }
|
sqlx = { workspace = true, optional = true }
|
||||||
tower-http = { workspace = true, optional = true }
|
tower-http = { workspace = true, optional = true }
|
||||||
mime-sniffer = { version = "0.1", optional = true }
|
mime-sniffer = { version = "0.1", optional = true }
|
||||||
|
|
@ -32,8 +32,8 @@ axum-extra = { workspace = true, optional = true }
|
||||||
anyhow = { workspace = true, optional = true }
|
anyhow = { workspace = true, optional = true }
|
||||||
bytes = { workspace = true, optional = true }
|
bytes = { workspace = true, optional = true }
|
||||||
fast_qr = { workspace = true, optional = true }
|
fast_qr = { workspace = true, optional = true }
|
||||||
|
argon2 = { workspace = true, optional = true }
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
api.workspace = true
|
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
werewolves-macros.workspace = true
|
werewolves-macros.workspace = true
|
||||||
|
|
@ -53,6 +53,7 @@ hydrate = [
|
||||||
ssr = [
|
ssr = [
|
||||||
"dep:axum",
|
"dep:axum",
|
||||||
"dep:tokio",
|
"dep:tokio",
|
||||||
|
"dep:argon2",
|
||||||
"dep:leptos_axum",
|
"dep:leptos_axum",
|
||||||
"dep:colored",
|
"dep:colored",
|
||||||
"dep:pretty_env_logger",
|
"dep:pretty_env_logger",
|
||||||
|
|
@ -70,7 +71,7 @@ ssr = [
|
||||||
"leptos_router/ssr",
|
"leptos_router/ssr",
|
||||||
"leptos-use/ssr",
|
"leptos-use/ssr",
|
||||||
"leptos-use/axum",
|
"leptos-use/axum",
|
||||||
"api/ssr",
|
"werewolves-proto/ssr",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Defines a size-optimized profile for the WASM bundle in release mode
|
# Defines a size-optimized profile for the WASM bundle in release mode
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use api::error::ServerError;
|
|
||||||
use leptos::{html::Div, prelude::*};
|
use leptos::{html::Div, prelude::*};
|
||||||
use leptos_use::{
|
use leptos_use::{
|
||||||
UseDraggableOptions, UseDraggableReturn, core::Position, use_draggable_with_options,
|
UseDraggableOptions, UseDraggableReturn, core::Position, use_draggable_with_options,
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,31 @@
|
||||||
use core::ops::Not;
|
use core::ops::Not;
|
||||||
|
|
||||||
use api::{error::ServerError, game::GameId, token::TokenString};
|
|
||||||
use leptos::{ev::MouseEvent, prelude::*};
|
use leptos::{ev::MouseEvent, prelude::*};
|
||||||
use leptos_router::hooks::use_url;
|
use leptos_router::hooks::use_url;
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
|
use werewolves_proto::{error::ServerError, game::GameId, token::TokenString};
|
||||||
|
|
||||||
use crate::app::{
|
use crate::{
|
||||||
components::{DialogModal, LinkButton},
|
app::{
|
||||||
|
components::LinkButton,
|
||||||
storage::user::{AuthContext, AuthContextStoreFields},
|
storage::user::{AuthContext, AuthContextStoreFields},
|
||||||
|
},
|
||||||
|
db::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
pub async fn get_active_game(token: TokenString) -> Result<Option<GameId>, ServerError> {
|
pub async fn get_active_game(token: TokenString) -> Result<Option<GameId>, ServerError> {
|
||||||
let db = expect_context::<api::state::AppState>().db;
|
let db = expect_context::<AppState>().db;
|
||||||
let user = db.user().check_token(&token).await?;
|
let user = db.user().check_token(&token).await?;
|
||||||
Ok(db.game().get_joined_active_game(user.id).await?)
|
let Some(hosted) = db.game().get_hosted_active_game(user.id).await? else {
|
||||||
|
return Ok(db.game().get_joined_active_game(user.id).await?);
|
||||||
|
};
|
||||||
|
Ok(Some(hosted))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
pub async fn new_game(token: TokenString) -> Result<GameId, ServerError> {
|
pub async fn new_game(token: TokenString) -> Result<GameId, ServerError> {
|
||||||
let db = expect_context::<api::state::AppState>().db;
|
let db = expect_context::<AppState>().db;
|
||||||
let user = db.user().check_token(&token).await?;
|
let user = db.user().check_token(&token).await?;
|
||||||
Ok(db.game().new_game(user.id).await?.id)
|
Ok(db.game().new_game(user.id).await?.id)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,6 @@ pub mod big;
|
||||||
mod host;
|
mod host;
|
||||||
mod player;
|
mod player;
|
||||||
|
|
||||||
use api::{
|
|
||||||
cbor_leptos::CborEncoding,
|
|
||||||
message::{IntoClientResponse, WrappedServerMessage},
|
|
||||||
};
|
|
||||||
use codee::{HybridEncoder, binary::MsgpackSerdeCodec};
|
use codee::{HybridEncoder, binary::MsgpackSerdeCodec};
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::hooks;
|
use leptos_router::hooks;
|
||||||
|
|
@ -19,9 +15,13 @@ use werewolves_proto::message::{
|
||||||
ClientMessage,
|
ClientMessage,
|
||||||
host::{HostMessage, ServerToHostMessage},
|
host::{HostMessage, ServerToHostMessage},
|
||||||
};
|
};
|
||||||
|
use werewolves_proto::{
|
||||||
|
cbor_leptos::CborEncoding,
|
||||||
|
message::{IntoClientResponse, WrappedServerMessage},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ConsoleLogError, LogError,
|
ConsoleLogError,
|
||||||
app::{
|
app::{
|
||||||
components::DebugMarker,
|
components::DebugMarker,
|
||||||
pages::game::{host::HostGamePage, player::PlayerGamePage},
|
pages::game::{host::HostGamePage, player::PlayerGamePage},
|
||||||
|
|
@ -32,17 +32,29 @@ use crate::{
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn GamePage() -> impl IntoView {
|
pub fn GamePage() -> impl IntoView {
|
||||||
|
move || {
|
||||||
let params = hooks::use_params_map();
|
let params = hooks::use_params_map();
|
||||||
let auth = expect_context::<Store<AuthContext>>();
|
let auth = expect_context::<Store<AuthContext>>();
|
||||||
let state = expect_context::<Store<SessionState>>();
|
let state = expect_context::<Store<SessionState>>();
|
||||||
Effect::new(move || {
|
Effect::new(move || {
|
||||||
params.read().get("id").unwrap_or_default();
|
params.read().get("id").unwrap_or_default();
|
||||||
});
|
});
|
||||||
|
let url = RwSignal::new(format!(
|
||||||
|
"/api/games/{}",
|
||||||
|
params.read().get("id").unwrap_or_default()
|
||||||
|
));
|
||||||
|
Effect::watch(
|
||||||
|
move || url.get(),
|
||||||
|
move |_, _, _| {
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
|
gloo::utils::window().location().reload().console_log_warn()
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
let UseWebSocketReturn {
|
let UseWebSocketReturn {
|
||||||
ready_state,
|
ready_state,
|
||||||
message,
|
message,
|
||||||
send,
|
send,
|
||||||
open,
|
|
||||||
close,
|
close,
|
||||||
..
|
..
|
||||||
} = use_websocket_with_options::<
|
} = use_websocket_with_options::<
|
||||||
|
|
@ -52,11 +64,10 @@ pub fn GamePage() -> impl IntoView {
|
||||||
_,
|
_,
|
||||||
_,
|
_,
|
||||||
>(
|
>(
|
||||||
format!(
|
#[cfg(not(feature = "ssr"))]
|
||||||
"/api/games/{}",
|
url.read().as_str(),
|
||||||
params.read_untracked().get("id").unwrap_or_default()
|
#[cfg(feature = "ssr")]
|
||||||
)
|
"",
|
||||||
.as_str(),
|
|
||||||
UseWebSocketOptions::default().reconnect_limit(ReconnectLimit::Infinite),
|
UseWebSocketOptions::default().reconnect_limit(ReconnectLimit::Infinite),
|
||||||
);
|
);
|
||||||
let opened = RwSignal::new(false);
|
let opened = RwSignal::new(false);
|
||||||
|
|
@ -186,4 +197,5 @@ pub fn GamePage() -> impl IntoView {
|
||||||
disconnect=disconnect
|
disconnect=disconnect
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use core::ops::Not;
|
use core::ops::Not;
|
||||||
|
|
||||||
use api::user::{Password, Session, Username};
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use gloo::history::History;
|
use gloo::history::History;
|
||||||
use leptos::{ev::MouseEvent, prelude::*};
|
use leptos::{ev::MouseEvent, prelude::*};
|
||||||
|
use werewolves_proto::player::{Password, Session, Username};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
use core::ops::Not;
|
use core::ops::Not;
|
||||||
|
|
||||||
use api::{
|
|
||||||
cbor_leptos::CborPost,
|
|
||||||
error::ServerError,
|
|
||||||
user::{Password, Session, Username},
|
|
||||||
};
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use gloo::history::History;
|
use gloo::history::History;
|
||||||
use leptos::{
|
use leptos::{
|
||||||
|
|
@ -14,6 +9,11 @@ use leptos::{
|
||||||
use leptos_meta::*;
|
use leptos_meta::*;
|
||||||
use rand::distr::SampleString;
|
use rand::distr::SampleString;
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
|
use werewolves_proto::{
|
||||||
|
cbor_leptos::CborPost,
|
||||||
|
error::ServerError,
|
||||||
|
player::{Password, PlayerId, Session, Username},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
|
|
@ -24,6 +24,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
auth::Signin,
|
auth::Signin,
|
||||||
|
db::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[leptos::server(CreateUser, input = CborPost)]
|
#[leptos::server(CreateUser, input = CborPost)]
|
||||||
|
|
@ -31,8 +32,8 @@ pub async fn create_user(
|
||||||
username: Username,
|
username: Username,
|
||||||
password: Password,
|
password: Password,
|
||||||
pronouns: Option<String>,
|
pronouns: Option<String>,
|
||||||
) -> core::result::Result<api::user::UserId, ServerError> {
|
) -> core::result::Result<PlayerId, ServerError> {
|
||||||
let user = use_context::<api::state::AppState>()
|
let user = use_context::<AppState>()
|
||||||
.expect("no app state")
|
.expect("no app state")
|
||||||
.db
|
.db
|
||||||
.user()
|
.user()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
use api::{cbor_leptos::CborPost, error::ServerError, user::Password};
|
|
||||||
use leptos::{ev::MouseEvent, prelude::*};
|
use leptos::{ev::MouseEvent, prelude::*};
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
|
use werewolves_proto::{cbor_leptos::CborPost, error::ServerError, player::Password};
|
||||||
|
|
||||||
use crate::app::{
|
use crate::{
|
||||||
|
app::{
|
||||||
components::{DialogModal, ErrorBox},
|
components::{DialogModal, ErrorBox},
|
||||||
storage::user::{AuthContext, AuthContextStoreFields},
|
storage::user::{AuthContext, AuthContextStoreFields},
|
||||||
|
},
|
||||||
|
db::AppState,
|
||||||
};
|
};
|
||||||
#[server(input = CborPost)]
|
#[server(input = CborPost)]
|
||||||
pub async fn change_password(
|
pub async fn change_password(
|
||||||
|
|
@ -12,10 +15,7 @@ pub async fn change_password(
|
||||||
current: Password,
|
current: Password,
|
||||||
new: Password,
|
new: Password,
|
||||||
) -> Result<(), ServerError> {
|
) -> Result<(), ServerError> {
|
||||||
let db = use_context::<api::state::AppState>()
|
let db = use_context::<AppState>().expect("no app state").db.user();
|
||||||
.expect("no app state")
|
|
||||||
.db
|
|
||||||
.user();
|
|
||||||
let user = db.check_token(token.as_str()).await?;
|
let user = db.check_token(token.as_str()).await?;
|
||||||
db.verify_login(&user.username, ¤t).await?;
|
db.verify_login(&user.username, ¤t).await?;
|
||||||
db.change_password(user.clone(), new).await?;
|
db.change_password(user.clone(), new).await?;
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,22 @@
|
||||||
use core::ops::Not;
|
use core::ops::Not;
|
||||||
|
|
||||||
use api::{
|
|
||||||
cbor_leptos::CborPost,
|
|
||||||
error::ServerError,
|
|
||||||
token::TokenString,
|
|
||||||
user::{ProfileUpdate, Session},
|
|
||||||
};
|
|
||||||
use leptos::{ev::MouseEvent, prelude::*};
|
use leptos::{ev::MouseEvent, prelude::*};
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
|
use werewolves_proto::{
|
||||||
|
cbor_leptos::CborPost, error::ServerError, player::ProfileUpdate, token::TokenString,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::app::{
|
use crate::{
|
||||||
|
app::{
|
||||||
components::{DialogModal, ErrorBox},
|
components::{DialogModal, ErrorBox},
|
||||||
storage::user::{AuthContext, AuthContextStoreFields},
|
storage::user::{AuthContext, AuthContextStoreFields},
|
||||||
|
},
|
||||||
|
db::AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[server(input = CborPost)]
|
#[server(input = CborPost)]
|
||||||
pub async fn update_profile(token: TokenString, update: ProfileUpdate) -> Result<(), ServerError> {
|
pub async fn update_profile(token: TokenString, update: ProfileUpdate) -> Result<(), ServerError> {
|
||||||
let db = use_context::<api::state::AppState>()
|
let db = use_context::<AppState>().expect("no app state").db.user();
|
||||||
.expect("no app state")
|
|
||||||
.db
|
|
||||||
.user();
|
|
||||||
let user = db.check_token(&token).await?;
|
let user = db.check_token(&token).await?;
|
||||||
db.update_profile(user.id, update).await?;
|
db.update_profile(user.id, update).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
use api::{
|
|
||||||
limited::FixedLenString,
|
|
||||||
token::TOKEN_LEN,
|
|
||||||
user::{Password, Session, Username},
|
|
||||||
};
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use leptos::{
|
use leptos::{
|
||||||
prelude::{Action, ArcRwSignal, Get, GetUntracked, Set},
|
prelude::{Action, ArcRwSignal, Get, GetUntracked, Set},
|
||||||
|
|
@ -10,6 +5,11 @@ use leptos::{
|
||||||
};
|
};
|
||||||
use reactive_stores::{Field, Patch, Store};
|
use reactive_stores::{Field, Patch, Store};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use werewolves_proto::{
|
||||||
|
limited::FixedLenString,
|
||||||
|
player::{Password, Session, Username},
|
||||||
|
token::TOKEN_LEN,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::storage::{LocalStorage, SessionStorage, Stored},
|
app::storage::{LocalStorage, SessionStorage, Stored},
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,20 @@
|
||||||
use api::{
|
|
||||||
cbor_leptos::CborPost,
|
|
||||||
error::ServerError,
|
|
||||||
limited::{ClampedString, FixedLenString},
|
|
||||||
token::{TOKEN_LEN, Token, TokenString},
|
|
||||||
user::{Password, Session, Username},
|
|
||||||
};
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use reactive_stores::Store;
|
use reactive_stores::Store;
|
||||||
|
use werewolves_proto::{
|
||||||
|
cbor_leptos::CborPost,
|
||||||
|
error::ServerError,
|
||||||
|
limited::{ClampedString, FixedLenString},
|
||||||
|
player::{Password, Session, Username},
|
||||||
|
token::{TOKEN_LEN, Token, TokenString},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::app::storage::{
|
use crate::app::storage::{
|
||||||
LocalStorage,
|
LocalStorage,
|
||||||
user::{AuthContext, AuthContextStoreFields, PasswordlessUser, UserSession, UserToken},
|
user::{AuthContext, AuthContextStoreFields, PasswordlessUser, UserSession, UserToken},
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
use crate::db::{AppState, user::GetUserBy};
|
||||||
|
|
||||||
pub async fn try_auto_signin(ctx: Store<AuthContext>) -> Result<(), ServerError> {
|
pub async fn try_auto_signin(ctx: Store<AuthContext>) -> Result<(), ServerError> {
|
||||||
if let Some(sess) = ctx.session().read().as_ref() {
|
if let Some(sess) = ctx.session().read().as_ref() {
|
||||||
|
|
@ -82,10 +84,7 @@ pub async fn replace_token(
|
||||||
|
|
||||||
#[leptos::server(Signin, input = CborPost)]
|
#[leptos::server(Signin, input = CborPost)]
|
||||||
pub async fn signin(username: Username, password: Password) -> Result<Session, ServerError> {
|
pub async fn signin(username: Username, password: Password) -> Result<Session, ServerError> {
|
||||||
let db = use_context::<api::state::AppState>()
|
let db = use_context::<AppState>().expect("no app state").db.user();
|
||||||
.expect("no app state")
|
|
||||||
.db
|
|
||||||
.user();
|
|
||||||
let sess = db.login(&username, &password).await?;
|
let sess = db.login(&username, &password).await?;
|
||||||
|
|
||||||
log::info!("user logged in: {}", sess.username);
|
log::info!("user logged in: {}", sess.username);
|
||||||
|
|
@ -95,7 +94,7 @@ pub async fn signin(username: Username, password: Password) -> Result<Session, S
|
||||||
|
|
||||||
#[server(VerifyToken, input = CborPost)]
|
#[server(VerifyToken, input = CborPost)]
|
||||||
pub async fn verify_token(token: FixedLenString<TOKEN_LEN>) -> Result<(), ServerError> {
|
pub async fn verify_token(token: FixedLenString<TOKEN_LEN>) -> Result<(), ServerError> {
|
||||||
use_context::<api::state::AppState>()
|
use_context::<AppState>()
|
||||||
.expect("no app state")
|
.expect("no app state")
|
||||||
.db
|
.db
|
||||||
.user()
|
.user()
|
||||||
|
|
@ -106,10 +105,7 @@ pub async fn verify_token(token: FixedLenString<TOKEN_LEN>) -> Result<(), Server
|
||||||
|
|
||||||
#[server(input = CborPost)]
|
#[server(input = CborPost)]
|
||||||
pub async fn get_me(token: FixedLenString<TOKEN_LEN>) -> Result<Session, ServerError> {
|
pub async fn get_me(token: FixedLenString<TOKEN_LEN>) -> Result<Session, ServerError> {
|
||||||
let db = use_context::<api::state::AppState>()
|
let db = use_context::<AppState>().expect("no app state").db.user();
|
||||||
.expect("no app state")
|
|
||||||
.db
|
|
||||||
.user();
|
|
||||||
db.check_token(&token).await?;
|
db.check_token(&token).await?;
|
||||||
let sess = db.get_session(&token).await?;
|
let sess = db.get_session(&token).await?;
|
||||||
|
|
||||||
|
|
@ -118,13 +114,10 @@ pub async fn get_me(token: FixedLenString<TOKEN_LEN>) -> Result<Session, ServerE
|
||||||
|
|
||||||
#[server(input = CborPost)]
|
#[server(input = CborPost)]
|
||||||
pub async fn replace_auth_token(token: FixedLenString<TOKEN_LEN>) -> Result<Token, ServerError> {
|
pub async fn replace_auth_token(token: FixedLenString<TOKEN_LEN>) -> Result<Token, ServerError> {
|
||||||
let db = use_context::<api::state::AppState>()
|
let db = use_context::<AppState>().expect("no app state").db.user();
|
||||||
.expect("no app state")
|
|
||||||
.db
|
|
||||||
.user();
|
|
||||||
let token = db.replace_token(&token).await?;
|
let token = db.replace_token(&token).await?;
|
||||||
let user = db
|
let user = db
|
||||||
.get_user(api::db::user::GetUserBy::Id(token.user_id))
|
.get_user(GetUserBy::Id(token.user_id))
|
||||||
.await
|
.await
|
||||||
.map_err(Into::<ServerError>::into)?;
|
.map_err(Into::<ServerError>::into)?;
|
||||||
Ok(Token {
|
Ok(Token {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
pub mod game;
|
pub mod game;
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
use futures::{future::BoxFuture, stream::BoxStream};
|
use leptos::config::LeptosOptions;
|
||||||
use sqlx::{Executor, Pool, Postgres, Transaction};
|
#[cfg(feature = "ssr")]
|
||||||
|
use sqlx::{Pool, Postgres};
|
||||||
|
use werewolves_proto::error::DatabaseError;
|
||||||
|
|
||||||
use crate::{
|
#[cfg(feature = "ssr")]
|
||||||
db::{game::GameDatabase, user::UserDatabase},
|
use {game::GameDatabase, user::UserDatabase};
|
||||||
error::DatabaseError,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) type DatabaseResult<T> = core::result::Result<T, DatabaseError>;
|
pub(crate) type DatabaseResult<T> = core::result::Result<T, DatabaseError>;
|
||||||
|
|
||||||
|
|
@ -15,13 +17,15 @@ trait IntoDatabaseResult<T> {
|
||||||
fn into_db_result(self) -> DatabaseResult<T>;
|
fn into_db_result(self) -> DatabaseResult<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
impl<T> IntoDatabaseResult<T> for Result<T, ::sqlx::Error> {
|
impl<T> IntoDatabaseResult<T> for Result<T, ::sqlx::Error> {
|
||||||
fn into_db_result(self) -> DatabaseResult<T> {
|
fn into_db_result(self) -> DatabaseResult<T> {
|
||||||
self.map_err(Into::<DatabaseError>::into)
|
self.map_err(Into::<DatabaseError>::into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> IntoDatabaseResult<T> for Result<T, ::serde_json::Error> {
|
#[cfg(feature = "ssr")]
|
||||||
|
impl<T> IntoDatabaseResult<T> for Result<T, leptos::serde_json::Error> {
|
||||||
fn into_db_result(self) -> DatabaseResult<T> {
|
fn into_db_result(self) -> DatabaseResult<T> {
|
||||||
self.map_err(Into::<DatabaseError>::into)
|
self.map_err(Into::<DatabaseError>::into)
|
||||||
}
|
}
|
||||||
|
|
@ -29,9 +33,11 @@ impl<T> IntoDatabaseResult<T> for Result<T, ::serde_json::Error> {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
pool: Pool<Postgres>,
|
pool: Pool<Postgres>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
impl Database {
|
impl Database {
|
||||||
pub const fn new(pool: Pool<Postgres>) -> Self {
|
pub const fn new(pool: Pool<Postgres>) -> Self {
|
||||||
Self { pool }
|
Self { pool }
|
||||||
|
|
@ -58,3 +64,10 @@ impl Database {
|
||||||
log::info!("migrations done");
|
log::info!("migrations done");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "ssr", derive(axum::extract::FromRef))]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub db: Database,
|
||||||
|
pub leptos_options: LeptosOptions,
|
||||||
|
}
|
||||||
|
|
@ -1,24 +1,18 @@
|
||||||
use core::num::NonZeroU8;
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use futures::executor;
|
use sqlx::{Pool, Postgres, query};
|
||||||
use sqlx::{Pool, Postgres, query, query_as};
|
|
||||||
use werewolves_proto::{
|
use werewolves_proto::{
|
||||||
|
ServerResult,
|
||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
error::GameError,
|
error::{DatabaseError, GameError, ServerError},
|
||||||
game::{Game, GameSettings},
|
game::{Game, GameId, GameSettings},
|
||||||
message::{CharacterIdentity, Identification, PublicIdentity, dead::DeadChatMessage},
|
game_record::{GameRecord, GameRecordState},
|
||||||
player::PlayerId,
|
message::{Identification, PublicIdentity, dead::DeadChatMessage},
|
||||||
|
player::{PlayerId, PlayerIdentity},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::db::{DatabaseResult, IntoDatabaseResult};
|
||||||
ServerResult,
|
|
||||||
db::{DatabaseResult, IntoDatabaseResult},
|
|
||||||
error::{DatabaseError, ServerError},
|
|
||||||
game::{GameId, GameRecord, GameRecordState},
|
|
||||||
identity::PlayerIdentity,
|
|
||||||
user::UserId,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct GameDatabase {
|
pub struct GameDatabase {
|
||||||
|
|
@ -30,14 +24,14 @@ impl GameDatabase {
|
||||||
Self { pool }
|
Self { pool }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new_game(&self, host: UserId) -> DatabaseResult<GameRecord> {
|
pub async fn new_game(&self, host: PlayerId) -> DatabaseResult<GameRecord> {
|
||||||
let record = GameRecord {
|
let record = GameRecord {
|
||||||
host,
|
host,
|
||||||
id: GameId::new(),
|
id: GameId::new(),
|
||||||
created_at: Utc::now(),
|
created_at: Utc::now(),
|
||||||
game_state: crate::game::GameRecordState::Lobby(GameSettings::default()),
|
game_state: GameRecordState::Lobby(GameSettings::default()),
|
||||||
};
|
};
|
||||||
let game_state_json = serde_json::to_value(&record.game_state)
|
let game_state_json = leptos::serde_json::to_value(&record.game_state)
|
||||||
.map_err(|err| DatabaseError::Serialization(err.to_string()))?;
|
.map_err(|err| DatabaseError::Serialization(err.to_string()))?;
|
||||||
query!(
|
query!(
|
||||||
r#"
|
r#"
|
||||||
|
|
@ -123,9 +117,9 @@ impl GameDatabase {
|
||||||
.await?;
|
.await?;
|
||||||
Ok(GameRecord {
|
Ok(GameRecord {
|
||||||
id: GameId::from_uuid(r.id),
|
id: GameId::from_uuid(r.id),
|
||||||
host: UserId::from_uuid(r.host),
|
host: PlayerId::from_uuid(r.host),
|
||||||
created_at: r.created_at,
|
created_at: r.created_at,
|
||||||
game_state: serde_json::from_value(r.game_state)?,
|
game_state: leptos::serde_json::from_value(r.game_state)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,7 +135,7 @@ impl GameDatabase {
|
||||||
where
|
where
|
||||||
E: ::sqlx::Executor<'a, Database = Postgres>,
|
E: ::sqlx::Executor<'a, Database = Postgres>,
|
||||||
{
|
{
|
||||||
let game_state_json = serde_json::to_value(&record.game_state)
|
let game_state_json = leptos::serde_json::to_value(&record.game_state)
|
||||||
.map_err(|err| DatabaseError::Serialization(err.to_string()))?;
|
.map_err(|err| DatabaseError::Serialization(err.to_string()))?;
|
||||||
let game_status = match &record.game_state {
|
let game_status = match &record.game_state {
|
||||||
GameRecordState::Lobby(_) => "Lobby",
|
GameRecordState::Lobby(_) => "Lobby",
|
||||||
|
|
@ -171,7 +165,7 @@ impl GameDatabase {
|
||||||
pub async fn get_player_number(
|
pub async fn get_player_number(
|
||||||
&self,
|
&self,
|
||||||
game: GameId,
|
game: GameId,
|
||||||
player: UserId,
|
player: PlayerId,
|
||||||
) -> DatabaseResult<Option<NonZeroU8>> {
|
) -> DatabaseResult<Option<NonZeroU8>> {
|
||||||
Ok(query!(
|
Ok(query!(
|
||||||
r#"
|
r#"
|
||||||
|
|
@ -194,7 +188,7 @@ impl GameDatabase {
|
||||||
pub async fn set_player_number(
|
pub async fn set_player_number(
|
||||||
&self,
|
&self,
|
||||||
game: GameId,
|
game: GameId,
|
||||||
player: UserId,
|
player: PlayerId,
|
||||||
number: Option<NonZeroU8>,
|
number: Option<NonZeroU8>,
|
||||||
) -> DatabaseResult<()> {
|
) -> DatabaseResult<()> {
|
||||||
query!(
|
query!(
|
||||||
|
|
@ -215,7 +209,23 @@ impl GameDatabase {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_joined_active_game(&self, player: UserId) -> DatabaseResult<Option<GameId>> {
|
pub async fn get_hosted_active_game(&self, player: PlayerId) -> DatabaseResult<Option<GameId>> {
|
||||||
|
Ok(query!(
|
||||||
|
r#"
|
||||||
|
select
|
||||||
|
id
|
||||||
|
from
|
||||||
|
games
|
||||||
|
where
|
||||||
|
host = $1 and game_status in ('Lobby', 'RoleReveal', 'Started')"#,
|
||||||
|
player.into_uuid(),
|
||||||
|
)
|
||||||
|
.fetch_optional(&self.pool)
|
||||||
|
.await?
|
||||||
|
.map(|d| GameId::from_uuid(d.id)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_joined_active_game(&self, player: PlayerId) -> DatabaseResult<Option<GameId>> {
|
||||||
Ok(query!(
|
Ok(query!(
|
||||||
r#"
|
r#"
|
||||||
select
|
select
|
||||||
|
|
@ -236,7 +246,7 @@ impl GameDatabase {
|
||||||
pub async fn join_game(
|
pub async fn join_game(
|
||||||
&self,
|
&self,
|
||||||
game: GameId,
|
game: GameId,
|
||||||
player: UserId,
|
player: PlayerId,
|
||||||
number: Option<NonZeroU8>,
|
number: Option<NonZeroU8>,
|
||||||
) -> ServerResult<()> {
|
) -> ServerResult<()> {
|
||||||
let game = self.get_game(game).await?;
|
let game = self.get_game(game).await?;
|
||||||
|
|
@ -246,9 +256,12 @@ impl GameDatabase {
|
||||||
if !matches!(game.game_state, GameRecordState::Lobby(_)) {
|
if !matches!(game.game_state, GameRecordState::Lobby(_)) {
|
||||||
return Err(GameError::CannotJoinStartedGame.into());
|
return Err(GameError::CannotJoinStartedGame.into());
|
||||||
}
|
}
|
||||||
|
if let Some(hosted) = self.get_hosted_active_game(player).await? {
|
||||||
|
return Err(ServerError::AlreadyInActiveGame(hosted));
|
||||||
|
}
|
||||||
if let Some(active) = self.get_joined_active_game(player).await? {
|
if let Some(active) = self.get_joined_active_game(player).await? {
|
||||||
return Err(ServerError::AlreadyInActiveGame(active));
|
return Err(ServerError::AlreadyInActiveGame(active));
|
||||||
};
|
}
|
||||||
query!(
|
query!(
|
||||||
r#"
|
r#"
|
||||||
insert into
|
insert into
|
||||||
|
|
@ -267,7 +280,7 @@ impl GameDatabase {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn leave_game(&self, game: GameId, player: UserId) -> ServerResult<()> {
|
pub async fn leave_game(&self, game: GameId, player: PlayerId) -> ServerResult<()> {
|
||||||
query!(
|
query!(
|
||||||
r#"
|
r#"
|
||||||
delete from
|
delete from
|
||||||
|
|
@ -340,7 +353,7 @@ impl GameDatabase {
|
||||||
game_id: GameId,
|
game_id: GameId,
|
||||||
message: DeadChatMessage,
|
message: DeadChatMessage,
|
||||||
) -> ServerResult<()> {
|
) -> ServerResult<()> {
|
||||||
let content = serde_json::to_value(&message.message).into_db_result()?;
|
let content = leptos::serde_json::to_value(&message.message).into_db_result()?;
|
||||||
query!(
|
query!(
|
||||||
r#"
|
r#"
|
||||||
insert into
|
insert into
|
||||||
|
|
@ -381,7 +394,7 @@ impl GameDatabase {
|
||||||
Ok(DeadChatMessage {
|
Ok(DeadChatMessage {
|
||||||
id: r.message_id,
|
id: r.message_id,
|
||||||
timestamp: r.created_at,
|
timestamp: r.created_at,
|
||||||
message: serde_json::from_value(r.message).into_db_result()?,
|
message: leptos::serde_json::from_value(r.message).into_db_result()?,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect::<Result<Box<[_]>, _>>()?)
|
.collect::<Result<Box<[_]>, _>>()?)
|
||||||
|
|
@ -5,18 +5,19 @@ use argon2::{
|
||||||
use chrono::{DateTime, TimeDelta, Utc};
|
use chrono::{DateTime, TimeDelta, Utc};
|
||||||
|
|
||||||
use rand::distr::SampleString;
|
use rand::distr::SampleString;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlx::{Decode, Encode, Pool, Postgres, prelude::FromRow, query, query_as};
|
use sqlx::{Decode, Encode, Pool, Postgres, prelude::FromRow, query, query_as};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use werewolves_proto::player::PlayerId;
|
||||||
|
|
||||||
use crate::{
|
use werewolves_proto::{
|
||||||
db::DatabaseResult,
|
|
||||||
error::{DatabaseError, ServerError},
|
error::{DatabaseError, ServerError},
|
||||||
limited::FixedLenString,
|
limited::FixedLenString,
|
||||||
|
player::{Password, ProfileUpdate, Session},
|
||||||
token::{self, TOKEN_LEN},
|
token::{self, TOKEN_LEN},
|
||||||
user::{Password, ProfileUpdate, Session, UserId},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::db::DatabaseResult;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UserDatabase {
|
pub struct UserDatabase {
|
||||||
pub(super) pool: Pool<Postgres>,
|
pub(super) pool: Pool<Postgres>,
|
||||||
|
|
@ -25,7 +26,7 @@ pub struct UserDatabase {
|
||||||
#[derive(Debug, Clone, FromRow)]
|
#[derive(Debug, Clone, FromRow)]
|
||||||
pub struct LoginToken {
|
pub struct LoginToken {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub user_id: UserId,
|
pub user_id: PlayerId,
|
||||||
|
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
pub expires_at: DateTime<Utc>,
|
pub expires_at: DateTime<Utc>,
|
||||||
|
|
@ -34,7 +35,7 @@ pub struct LoginToken {
|
||||||
impl LoginToken {
|
impl LoginToken {
|
||||||
pub const TOKEN_LONGEVITY: TimeDelta = TimeDelta::days(30);
|
pub const TOKEN_LONGEVITY: TimeDelta = TimeDelta::days(30);
|
||||||
|
|
||||||
pub fn new(user_id: UserId) -> Self {
|
pub fn new(user_id: PlayerId) -> Self {
|
||||||
let created_at = Utc::now();
|
let created_at = Utc::now();
|
||||||
let expires_at = created_at
|
let expires_at = created_at
|
||||||
.checked_add_signed(Self::TOKEN_LONGEVITY)
|
.checked_add_signed(Self::TOKEN_LONGEVITY)
|
||||||
|
|
@ -58,7 +59,7 @@ impl LoginToken {
|
||||||
|
|
||||||
pub enum GetUserBy<'a> {
|
pub enum GetUserBy<'a> {
|
||||||
Username(&'a str),
|
Username(&'a str),
|
||||||
Id(UserId),
|
Id(PlayerId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserDatabase {
|
impl UserDatabase {
|
||||||
|
|
@ -99,7 +100,7 @@ impl UserDatabase {
|
||||||
created_at,
|
created_at,
|
||||||
updated_at,
|
updated_at,
|
||||||
password_hash,
|
password_hash,
|
||||||
id: UserId::from_uuid(id),
|
id: PlayerId::from_uuid(id),
|
||||||
display_name: Some(display_name.to_string()),
|
display_name: Some(display_name.to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -120,7 +121,7 @@ impl UserDatabase {
|
||||||
|
|
||||||
let user = User {
|
let user = User {
|
||||||
pronouns,
|
pronouns,
|
||||||
id: UserId::new(),
|
id: PlayerId::new(),
|
||||||
username: username.into(),
|
username: username.into(),
|
||||||
password_hash,
|
password_hash,
|
||||||
display_name: None,
|
display_name: None,
|
||||||
|
|
@ -173,7 +174,7 @@ impl UserDatabase {
|
||||||
|
|
||||||
pub async fn update_profile(
|
pub async fn update_profile(
|
||||||
&self,
|
&self,
|
||||||
user_id: UserId,
|
user_id: PlayerId,
|
||||||
update: ProfileUpdate,
|
update: ProfileUpdate,
|
||||||
) -> DatabaseResult<()> {
|
) -> DatabaseResult<()> {
|
||||||
query!(
|
query!(
|
||||||
|
|
@ -421,7 +422,7 @@ impl UserDatabase {
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromRow, Encode, Decode)]
|
#[derive(Debug, Clone, FromRow, Encode, Decode)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: UserId,
|
pub id: PlayerId,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password_hash: String,
|
pub password_hash: String,
|
||||||
pub display_name: Option<String>,
|
pub display_name: Option<String>,
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
use core::{net::SocketAddr, str::FromStr};
|
use core::{net::SocketAddr, str::FromStr};
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod db;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
@ -32,64 +33,6 @@ pub fn hydrate() {
|
||||||
leptos::mount::hydrate_body(App);
|
leptos::mount::hydrate_body(App);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait LogError {
|
|
||||||
fn log(self, loc: CodePath, level: log::Level);
|
|
||||||
fn log_warn(self, loc: CodePath);
|
|
||||||
fn log_err(self, loc: CodePath);
|
|
||||||
fn log_debug(self, loc: CodePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CodePath {
|
|
||||||
pub module_path: &'static str,
|
|
||||||
pub loc: &'static std::panic::Location<'static>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! loc {
|
|
||||||
() => {
|
|
||||||
(crate::CodePath {
|
|
||||||
module_path: log::__private_api::module_path!(),
|
|
||||||
loc: log::__private_api::loc(),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, E> LogError for Result<T, E>
|
|
||||||
where
|
|
||||||
E: core::fmt::Display,
|
|
||||||
{
|
|
||||||
fn log(self, loc: CodePath, lvl: log::Level) {
|
|
||||||
if let Err(err) = self {
|
|
||||||
if lvl <= log::STATIC_MAX_LEVEL && lvl <= log::max_level() {
|
|
||||||
log::__private_api::log(
|
|
||||||
log::__log_logger!(__log_global_logger),
|
|
||||||
log::__private_api::format_args!("{err}"),
|
|
||||||
lvl,
|
|
||||||
&(loc.module_path, loc.module_path, loc.loc),
|
|
||||||
(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_warn(self, loc: CodePath) {
|
|
||||||
if self.is_err() {
|
|
||||||
self.log(loc, log::Level::Warn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_err(self, loc: CodePath) {
|
|
||||||
if self.is_err() {
|
|
||||||
self.log(loc, log::Level::Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_debug(self, loc: CodePath) {
|
|
||||||
if self.is_err() {
|
|
||||||
self.log(loc, log::Level::Debug);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub trait ConsoleLogError {
|
pub trait ConsoleLogError {
|
||||||
fn console_log_warn(self);
|
fn console_log_warn(self);
|
||||||
fn console_log_err(self);
|
fn console_log_err(self);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#![allow(clippy::expect_fun_call)]
|
#![allow(clippy::expect_fun_call)]
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
mod ssr {
|
mod ssr {
|
||||||
pub const DEFAULT_MAX_PG_CONNECTIONS: u32 = 30;
|
pub const DEFAULT_MAX_PG_CONNECTIONS: u32 = 30;
|
||||||
|
|
@ -6,7 +7,6 @@ mod ssr {
|
||||||
|
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
|
|
||||||
use api::state::AppState;
|
|
||||||
use axum::{
|
use axum::{
|
||||||
body::Body,
|
body::Body,
|
||||||
extract::{FromRef, State},
|
extract::{FromRef, State},
|
||||||
|
|
@ -22,6 +22,7 @@ mod ssr {
|
||||||
tachys::ssr::StreamBuilder,
|
tachys::ssr::StreamBuilder,
|
||||||
};
|
};
|
||||||
use leptos_axum::{LeptosRoutes, ResponseOptions};
|
use leptos_axum::{LeptosRoutes, ResponseOptions};
|
||||||
|
use werewolves::db::AppState;
|
||||||
|
|
||||||
pub async fn server_fn_handler(
|
pub async fn server_fn_handler(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
|
|
@ -113,7 +114,6 @@ mod ssr {
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
use api::{db::Database, state::AppState};
|
|
||||||
use axum::ServiceExt;
|
use axum::ServiceExt;
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum::{Router, routing::any};
|
use axum::{Router, routing::any};
|
||||||
|
|
@ -125,6 +125,7 @@ async fn main() {
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use werewolves::app::*;
|
use werewolves::app::*;
|
||||||
|
use werewolves::db::{AppState, Database};
|
||||||
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
pretty_env_logger::formatted_builder()
|
pretty_env_logger::formatted_builder()
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::server::runner::{ClientUpdate, HostOrClientMessage, IdentifiedClientMessage};
|
use crate::server::runner::{ClientUpdate, HostOrClientMessage, IdentifiedClientMessage};
|
||||||
use api::game::GameId;
|
use werewolves_proto::game::GameId;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use werewolves_proto::{
|
use werewolves_proto::{
|
||||||
error::GameError,
|
error::GameError,
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,6 @@
|
||||||
use core::{net::SocketAddr, str::FromStr};
|
use core::{net::SocketAddr, str::FromStr};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use api::{
|
|
||||||
error::ServerError, game::GameId, message::WrappedServerMessage, state::AppState,
|
|
||||||
token::TokenString,
|
|
||||||
};
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{ConnectInfo, Path, State, WebSocketUpgrade, ws::WebSocket},
|
extract::{ConnectInfo, Path, State, WebSocketUpgrade, ws::WebSocket},
|
||||||
response::Response,
|
response::Response,
|
||||||
|
|
@ -32,8 +28,11 @@ use colored::Colorize;
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use werewolves_proto::{error::GameError, message::ServerToClientMessage};
|
use werewolves_proto::{error::GameError, message::ServerToClientMessage};
|
||||||
|
use werewolves_proto::{
|
||||||
|
error::ServerError, game::GameId, message::WrappedServerMessage, token::TokenString,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{LogError, server::XForwardedFor};
|
use crate::{db::AppState, server::XForwardedFor};
|
||||||
|
|
||||||
pub async fn handler(
|
pub async fn handler(
|
||||||
ws: WebSocketUpgrade,
|
ws: WebSocketUpgrade,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use api::game::GameId;
|
use werewolves_proto::game::GameId;
|
||||||
use werewolves_proto::{
|
use werewolves_proto::{
|
||||||
game::story::GameStory,
|
game::story::GameStory,
|
||||||
message::{
|
message::{
|
||||||
|
|
|
||||||
|
|
@ -14,19 +14,20 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use api::{
|
|
||||||
db::{Database, user::User},
|
|
||||||
error::ServerError,
|
|
||||||
game::GameRecord,
|
|
||||||
message::{IntoClientResponse, WrappedServerMessage},
|
|
||||||
};
|
|
||||||
use axum::extract::ws::{self, Message, WebSocket};
|
use axum::extract::ws::{self, Message, WebSocket};
|
||||||
use codee::{HybridDecoder, HybridEncoder};
|
use codee::{HybridDecoder, HybridEncoder};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use tokio::sync::{broadcast, mpsc::UnboundedSender};
|
use tokio::sync::{broadcast, mpsc::UnboundedSender};
|
||||||
|
use werewolves_proto::game_record::GameRecord;
|
||||||
use werewolves_proto::message::host::{HostMessage, ServerToHostMessage};
|
use werewolves_proto::message::host::{HostMessage, ServerToHostMessage};
|
||||||
|
use werewolves_proto::{LogError, loc};
|
||||||
|
use werewolves_proto::{
|
||||||
|
error::ServerError,
|
||||||
|
message::{IntoClientResponse, WrappedServerMessage},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{LogError, loc};
|
use crate::db::Database;
|
||||||
|
use crate::db::user::User;
|
||||||
|
|
||||||
pub async fn host_handler(
|
pub async fn host_handler(
|
||||||
mut ws: WebSocket,
|
mut ws: WebSocket,
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,14 @@ use core::{
|
||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use api::{
|
|
||||||
db::Database,
|
|
||||||
error::ServerError,
|
|
||||||
game::{GameId, GameRecord},
|
|
||||||
};
|
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use tokio::sync::broadcast::Sender;
|
use tokio::sync::broadcast::Sender;
|
||||||
use werewolves_proto::{
|
use werewolves_proto::{
|
||||||
|
LogError,
|
||||||
error::GameError,
|
error::GameError,
|
||||||
game::GameSettings,
|
game::GameSettings,
|
||||||
|
game_record::GameRecord,
|
||||||
|
loc,
|
||||||
message::{
|
message::{
|
||||||
ClientMessage, Identification, PlayerState, PublicIdentity, ServerToClientMessage,
|
ClientMessage, Identification, PlayerState, PublicIdentity, ServerToClientMessage,
|
||||||
UpdateSelf,
|
UpdateSelf,
|
||||||
|
|
@ -35,9 +33,10 @@ use werewolves_proto::{
|
||||||
},
|
},
|
||||||
player::PlayerId,
|
player::PlayerId,
|
||||||
};
|
};
|
||||||
|
use werewolves_proto::{error::ServerError, game::GameId};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
LogError, loc,
|
db::Database,
|
||||||
server::runner::{ClientUpdate, HostOrClientMessage, IdentifiedClientMessage},
|
server::runner::{ClientUpdate, HostOrClientMessage, IdentifiedClientMessage},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -150,6 +149,21 @@ impl<'a> Lobby<'a> {
|
||||||
.await;
|
.await;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
Err((
|
||||||
|
HostOrClientMessage::Client(IdentifiedClientMessage {
|
||||||
|
identity: Identification { player_id, .. },
|
||||||
|
..
|
||||||
|
}),
|
||||||
|
ServerError::AlreadyInActiveGame(active_game),
|
||||||
|
)) => {
|
||||||
|
super::send_player(
|
||||||
|
self.game_id,
|
||||||
|
player_id,
|
||||||
|
ServerToClientMessage::Error(GameError::AlreadyInAnotherGame(active_game)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
None
|
||||||
|
}
|
||||||
Err((
|
Err((
|
||||||
HostOrClientMessage::Client(IdentifiedClientMessage {
|
HostOrClientMessage::Client(IdentifiedClientMessage {
|
||||||
identity: Identification { player_id, public },
|
identity: Identification { player_id, public },
|
||||||
|
|
@ -285,9 +299,7 @@ impl<'a> Lobby<'a> {
|
||||||
.game()
|
.game()
|
||||||
.set_player_number(self.game_id, player_id.into(), Some(number))
|
.set_player_number(self.game_id, player_id.into(), Some(number))
|
||||||
.await?;
|
.await?;
|
||||||
self.send_lobby_info_to_clients()
|
self.send_lobby_info_to_clients().await.log_debug(loc!());
|
||||||
.await
|
|
||||||
.log_debug(crate::loc!());
|
|
||||||
self.send_lobby_info_to_host().await.log_warn(loc!());
|
self.send_lobby_info_to_host().await.log_warn(loc!());
|
||||||
}
|
}
|
||||||
HostOrClientMessage::Client(IdentifiedClientMessage {
|
HostOrClientMessage::Client(IdentifiedClientMessage {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ use std::{
|
||||||
sync::{Arc, LazyLock},
|
sync::{Arc, LazyLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use api::{db::Database, game::GameId};
|
|
||||||
use axum::http::header;
|
use axum::http::header;
|
||||||
use axum_extra::headers;
|
use axum_extra::headers;
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
|
|
@ -33,6 +32,9 @@ use tokio::{
|
||||||
time::Instant,
|
time::Instant,
|
||||||
};
|
};
|
||||||
use werewolves_proto::{
|
use werewolves_proto::{
|
||||||
|
LogError,
|
||||||
|
game::GameId,
|
||||||
|
loc,
|
||||||
message::{
|
message::{
|
||||||
ServerToClientMessage,
|
ServerToClientMessage,
|
||||||
host::{HostMessage, ServerToHostMessage},
|
host::{HostMessage, ServerToHostMessage},
|
||||||
|
|
@ -40,7 +42,7 @@ use werewolves_proto::{
|
||||||
player::PlayerId,
|
player::PlayerId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{loc, server::runner::IdentifiedClientMessage};
|
use crate::{db::Database, server::runner::IdentifiedClientMessage};
|
||||||
|
|
||||||
pub struct XForwardedFor(String);
|
pub struct XForwardedFor(String);
|
||||||
|
|
||||||
|
|
@ -300,8 +302,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_error(err: api::error::ServerError, socket: &mut axum::extract::ws::WebSocket) {
|
async fn send_error(
|
||||||
use crate::LogError;
|
err: werewolves_proto::error::ServerError,
|
||||||
|
socket: &mut axum::extract::ws::WebSocket,
|
||||||
|
) {
|
||||||
use codee::HybridEncoder;
|
use codee::HybridEncoder;
|
||||||
socket
|
socket
|
||||||
.send(axum::extract::ws::Message::Binary(
|
.send(axum::extract::ws::Message::Binary(
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,6 @@ use core::{net::SocketAddr, time::Duration};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use api::{
|
|
||||||
db::{Database, user::User},
|
|
||||||
error::ServerError,
|
|
||||||
game::{GameId, GameRecord},
|
|
||||||
message::{IntoClientResponse, WrappedServerMessage},
|
|
||||||
state::AppState,
|
|
||||||
};
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{
|
extract::{
|
||||||
ConnectInfo, Path, State, WebSocketUpgrade,
|
ConnectInfo, Path, State, WebSocketUpgrade,
|
||||||
|
|
@ -38,12 +31,20 @@ use chrono::Utc;
|
||||||
use codee::{HybridDecoder, HybridEncoder};
|
use codee::{HybridDecoder, HybridEncoder};
|
||||||
use colored::Colorize;
|
use colored::Colorize;
|
||||||
use tokio::sync::{broadcast::Receiver, mpsc::UnboundedSender};
|
use tokio::sync::{broadcast::Receiver, mpsc::UnboundedSender};
|
||||||
use werewolves_proto::message::{
|
use werewolves_proto::{
|
||||||
ClientMessage, Identification, PublicIdentity, ServerToClientMessage, UpdateSelf,
|
LogError,
|
||||||
|
error::ServerError,
|
||||||
|
game::GameId,
|
||||||
|
loc,
|
||||||
|
message::{IntoClientResponse, WrappedServerMessage},
|
||||||
|
};
|
||||||
|
use werewolves_proto::{
|
||||||
|
game_record::GameRecord,
|
||||||
|
message::{ClientMessage, Identification, PublicIdentity, ServerToClientMessage, UpdateSelf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
LogError, loc,
|
db::{Database, user::User},
|
||||||
server::{
|
server::{
|
||||||
XForwardedFor,
|
XForwardedFor,
|
||||||
runner::{ClientUpdate, HostOrClientMessage, IdentifiedClientMessage},
|
runner::{ClientUpdate, HostOrClientMessage, IdentifiedClientMessage},
|
||||||
|
|
@ -100,42 +101,6 @@ pub async fn player_handler(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub async fn player_handler(
|
|
||||||
// ws: WebSocketUpgrade,
|
|
||||||
// who: String,
|
|
||||||
// db: Database,
|
|
||||||
// game: GameRecord,
|
|
||||||
// user: User,
|
|
||||||
// ) -> Result<Response, ServerError> {
|
|
||||||
// // finalize the upgrade process by returning upgrade callback.
|
|
||||||
// // we can customize the callback by sending additional info such as address.
|
|
||||||
// Ok(ws.on_upgrade(move |mut socket| async move {
|
|
||||||
// // log::debug!("connected {who} as {ident}");
|
|
||||||
|
|
||||||
// // state
|
|
||||||
// // .send
|
|
||||||
// // .send(IdentifiedClientMessage {
|
|
||||||
// // identity: ident.clone(),
|
|
||||||
// // update: ClientUpdate::ConnectStateUpdate,
|
|
||||||
// // })
|
|
||||||
// // .log_debug(loc!());
|
|
||||||
// let (send, recv) = super::new_player_connection(game.id, user.id.into(), db).await;
|
|
||||||
|
|
||||||
// Client::new(ident.clone(), socket, who.to_string(), send, recv)
|
|
||||||
// .run()
|
|
||||||
// .await;
|
|
||||||
|
|
||||||
// // player_list.disconnect(&connection_id).await;
|
|
||||||
// // state
|
|
||||||
// // .send
|
|
||||||
// // .send(IdentifiedClientMessage {
|
|
||||||
// // identity: ident.clone(),
|
|
||||||
// // update: ClientUpdate::ConnectStateUpdate,
|
|
||||||
// // })
|
|
||||||
// // .log_debug(loc!());
|
|
||||||
// }))
|
|
||||||
// }
|
|
||||||
|
|
||||||
async fn get_identification(
|
async fn get_identification(
|
||||||
socket: &mut WebSocket,
|
socket: &mut WebSocket,
|
||||||
who: &str,
|
who: &str,
|
||||||
|
|
@ -208,8 +173,6 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn on_recv(&mut self, msg: Result<Message, axum::Error>) -> Result<(), anyhow::Error> {
|
async fn on_recv(&mut self, msg: Result<Message, axum::Error>) -> Result<(), anyhow::Error> {
|
||||||
use crate::LogError;
|
|
||||||
|
|
||||||
let msg = match msg {
|
let msg = match msg {
|
||||||
Ok(msg) => msg,
|
Ok(msg) => msg,
|
||||||
Err(err) => return Err(err.into()),
|
Err(err) => return Err(err.into()),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use core::ops::Not;
|
use core::ops::Not;
|
||||||
|
|
||||||
use api::game::GameId;
|
use werewolves_proto::game::GameId;
|
||||||
use werewolves_proto::{
|
use werewolves_proto::{
|
||||||
game::Game,
|
game::Game,
|
||||||
message::{
|
message::{
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,14 @@
|
||||||
|
|
||||||
use core::num::NonZeroU8;
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
use api::{
|
|
||||||
db::Database,
|
|
||||||
game::{GameId, GameRecord, GameRecordState},
|
|
||||||
user::ProfileUpdate,
|
|
||||||
};
|
|
||||||
use tokio::sync::mpsc::UnboundedReceiver;
|
use tokio::sync::mpsc::UnboundedReceiver;
|
||||||
|
use werewolves_proto::game::GameId;
|
||||||
|
use werewolves_proto::game_record::{GameRecord, GameRecordState};
|
||||||
use werewolves_proto::message::{ClientMessage, Identification, host::HostMessage};
|
use werewolves_proto::message::{ClientMessage, Identification, host::HostMessage};
|
||||||
|
use werewolves_proto::{LogError, loc};
|
||||||
|
|
||||||
use crate::{
|
use crate::db::Database;
|
||||||
LogError, loc,
|
use crate::server::{game::GameRunner, game_end::GameEnd, lobby::Lobby, role_reveal::RoleReveal};
|
||||||
server::{game::GameRunner, game_end::GameEnd, lobby::Lobby, role_reveal::RoleReveal},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum ClientUpdate {
|
pub enum ClientUpdate {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue