Start user registration work
This commit is contained in:
parent
218ca0b953
commit
d4bc71e26f
220
Cargo.lock
generated
220
Cargo.lock
generated
@ -14,6 +14,15 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_macro"
|
||||
version = "0.1.0"
|
||||
@ -63,6 +72,18 @@ version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5"
|
||||
|
||||
[[package]]
|
||||
name = "bcrypt"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7e7c93a3fb23b2fdde989b2c9ec4dd153063ec81f408507f84c090cd91c6641"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"blowfish",
|
||||
"getrandom",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -76,7 +97,9 @@ dependencies = [
|
||||
"ansi_macro",
|
||||
"async-trait",
|
||||
"base64 0.20.0",
|
||||
"bcrypt",
|
||||
"blastmud_interfaces",
|
||||
"chrono",
|
||||
"deadpool",
|
||||
"deadpool-postgres",
|
||||
"futures",
|
||||
@ -134,6 +157,16 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blowfish"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "buf_redux"
|
||||
version = "0.8.4"
|
||||
@ -174,6 +207,42 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time 0.1.45",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
@ -185,6 +254,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.5"
|
||||
@ -204,6 +279,50 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cxxbridge-flags",
|
||||
"cxxbridge-macro",
|
||||
"link-cplusplus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxx-build"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"codespan-reporting",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-flags"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59"
|
||||
|
||||
[[package]]
|
||||
name = "cxxbridge-macro"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool"
|
||||
version = "0.9.5"
|
||||
@ -414,7 +533,7 @@ checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -549,6 +668,30 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
|
||||
dependencies = [
|
||||
"cxx",
|
||||
"cxx-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.3.0"
|
||||
@ -569,6 +712,15 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
@ -605,6 +757,15 @@ version = "0.2.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||
|
||||
[[package]]
|
||||
name = "link-cplusplus"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.9"
|
||||
@ -678,7 +839,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
@ -1102,6 +1263,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "scratch"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.14"
|
||||
@ -1225,7 +1392,7 @@ dependencies = [
|
||||
"atty",
|
||||
"colored",
|
||||
"log",
|
||||
"time",
|
||||
"time 0.3.17",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
@ -1313,6 +1480,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.38"
|
||||
@ -1333,6 +1509,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.17"
|
||||
@ -1580,6 +1767,12 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.4"
|
||||
@ -1666,6 +1859,12 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
@ -1752,6 +1951,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
@ -1814,3 +2022,9 @@ name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
|
||||
|
@ -29,3 +29,5 @@ phf = { version = "0.11.1", features = ["macros"] }
|
||||
async-trait = "0.1.60"
|
||||
nom = "7.1.1"
|
||||
ouroboros = "0.15.5"
|
||||
chrono = { version = "0.4.23", features = ["serde"] }
|
||||
bcrypt = "0.13.0"
|
||||
|
@ -9,6 +9,7 @@ use tokio_postgres::NoTls;
|
||||
use crate::message_handler::ListenerSession;
|
||||
use crate::DResult;
|
||||
use crate::models::session::Session;
|
||||
use crate::models::user::User;
|
||||
use serde_json;
|
||||
use futures::FutureExt;
|
||||
|
||||
@ -164,18 +165,34 @@ impl DBTrans {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_session_model(self: &Self, session: &ListenerSession) -> DResult<Option<Session>> {
|
||||
pub async fn get_session_user_model(self: &Self, session: &ListenerSession) -> DResult<Option<(Session, Option<User>)>> {
|
||||
match self.pg_trans()?
|
||||
.query_opt("SELECT details FROM sessions WHERE session = $1", &[&session.session])
|
||||
.query_opt("SELECT s.details AS sess_details, \
|
||||
u.details AS user_details FROM sessions s \
|
||||
LEFT JOIN users u ON u.current_session = s.session \
|
||||
WHERE s.session = $1", &[&session.session])
|
||||
.await? {
|
||||
None => Ok(None),
|
||||
Some(row) =>
|
||||
Ok(Some(serde_json::from_value(
|
||||
row.get("details")
|
||||
)?))
|
||||
Ok(Some(
|
||||
(serde_json::from_value(
|
||||
row.get("sess_details"))?,
|
||||
match row.get::<&str, Option<serde_json::Value>>("user_details") {
|
||||
None => None,
|
||||
Some(v) => serde_json::from_value(v)?
|
||||
})
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn save_session_model(self: &Self, session: &ListenerSession, details: &Session)
|
||||
-> DResult<()> {
|
||||
self.pg_trans()?
|
||||
.execute("UPDATE sessions SET details = $1 WHERE session = $2",
|
||||
&[&serde_json::to_value(details)?, &session.session]).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn commit(mut self: Self) -> DResult<()> {
|
||||
let trans_opt = self.with_trans_mut(|t| std::mem::replace(t, None));
|
||||
for trans in trans_opt {
|
||||
|
@ -11,8 +11,11 @@ pub async fn handle(session: &ListenerSession, source: String, pool: &DBPool) ->
|
||||
Welcome to <red>BlastMud<reset> - a text-based post-apocalyptic \
|
||||
game <bold>restricted to adults (18+)<reset>\r\n\
|
||||
Some commands to get you started:\r\n\
|
||||
\t<bold>register <lt>username> <lt>password> <lt>email><reset> to register as a new user.\r\n\
|
||||
\t<bold>register <lt>username> <lt>password> to register as a new user.\r\n\
|
||||
\t<bold>connect <lt>username> <lt>password><reset> to log in as an existing user.\r\n\
|
||||
\t<bold>help<reset> to learn more.\r\n"))).await?;
|
||||
\t<bold>help<reset> to learn more.\r\n\
|
||||
[Please note BlastMud is still under development. You are welcome to play as we \
|
||||
develop it, but note it might still have bugs, unimplemented features, and \
|
||||
unbalanced gameplay aspects].\r\n"))).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -4,17 +4,20 @@ use crate::db::{DBTrans, DBPool};
|
||||
use ansi_macro::ansi;
|
||||
use phf::phf_map;
|
||||
use async_trait::async_trait;
|
||||
use crate::models::session::Session;
|
||||
use crate::models::{session::Session, user::User};
|
||||
use log::warn;
|
||||
|
||||
mod parsing;
|
||||
mod ignore;
|
||||
mod help;
|
||||
mod quit;
|
||||
mod less_explicit_mode;
|
||||
mod register;
|
||||
|
||||
pub struct VerbContext<'l> {
|
||||
session: &'l ListenerSession,
|
||||
session_dat: &'l mut Session,
|
||||
user_dat: &'l mut Option<User>,
|
||||
trans: &'l DBTrans
|
||||
}
|
||||
|
||||
@ -26,10 +29,9 @@ use CommandHandlingError::*;
|
||||
|
||||
#[async_trait]
|
||||
pub trait UserVerb {
|
||||
async fn handle(self: &Self, ctx: &VerbContext, verb: &str, remaining: &str) -> UResult<()>;
|
||||
async fn handle(self: &Self, ctx: &mut VerbContext, verb: &str, remaining: &str) -> UResult<()>;
|
||||
}
|
||||
|
||||
pub type UserVerbRef = &'static (dyn UserVerb + Sync + Send);
|
||||
pub type UResult<A> = Result<A, CommandHandlingError>;
|
||||
|
||||
impl From<Box<dyn std::error::Error + Send + Sync>> for CommandHandlingError {
|
||||
@ -42,6 +44,9 @@ pub fn user_error<A>(msg: String) -> UResult<A> {
|
||||
Err(UserError(msg))
|
||||
}
|
||||
|
||||
|
||||
/* Verb registries list types of commands available in different circumstances. */
|
||||
pub type UserVerbRef = &'static (dyn UserVerb + Sync + Send);
|
||||
type UserVerbRegistry = phf::Map<&'static str, UserVerbRef>;
|
||||
|
||||
static ALWAYS_AVAILABLE_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
@ -50,10 +55,26 @@ static ALWAYS_AVAILABLE_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
"quit" => quit::VERB,
|
||||
};
|
||||
|
||||
static UNREGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
||||
"less_explicit_mode" => less_explicit_mode::VERB,
|
||||
"register" => register::VERB,
|
||||
};
|
||||
|
||||
fn resolve_handler(ctx: &VerbContext, cmd: &str) -> Option<&'static UserVerbRef> {
|
||||
let mut result = ALWAYS_AVAILABLE_COMMANDS.get(cmd);
|
||||
|
||||
match ctx.user_dat {
|
||||
None => { result = result.or_else(|| UNREGISTERED_COMMANDS.get(cmd)); }
|
||||
Some(_) => {}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub async fn handle(session: &ListenerSession, msg: &str, pool: &DBPool) -> DResult<()> {
|
||||
let (cmd, params) = parsing::parse_command_name(msg);
|
||||
let trans = pool.start_transaction().await?;
|
||||
let mut session_dat = match trans.get_session_model(session).await? {
|
||||
let (mut session_dat, mut user_dat) = match trans.get_session_user_model(session).await? {
|
||||
None => {
|
||||
// If the session has been cleaned up from the database, there is
|
||||
// nowhere to go from here, so just ignore it.
|
||||
@ -62,8 +83,10 @@ pub async fn handle(session: &ListenerSession, msg: &str, pool: &DBPool) -> DRes
|
||||
}
|
||||
Some(v) => v
|
||||
};
|
||||
let handler_opt = ALWAYS_AVAILABLE_COMMANDS.get(cmd);
|
||||
let ctx = VerbContext { session, trans: &trans, session_dat: &mut session_dat };
|
||||
|
||||
let mut ctx = VerbContext { session, trans: &trans, session_dat: &mut session_dat,
|
||||
user_dat: &mut user_dat };
|
||||
let handler_opt = resolve_handler(&ctx, cmd);
|
||||
|
||||
match handler_opt {
|
||||
None => {
|
||||
@ -74,7 +97,7 @@ pub async fn handle(session: &ListenerSession, msg: &str, pool: &DBPool) -> DRes
|
||||
).await?;
|
||||
}
|
||||
Some(handler) => {
|
||||
match handler.handle(&ctx, cmd, params).await {
|
||||
match handler.handle(&mut ctx, cmd, params).await {
|
||||
Ok(()) => {}
|
||||
Err(UserError(err_msg)) => {
|
||||
trans.queue_for_session(session, Some(&(err_msg + "\r\n"))).await?;
|
||||
|
@ -28,7 +28,7 @@ static EXPLICIT_HELP_PAGES: phf::Map<&'static str, &'static str> = phf_map! {
|
||||
pub struct Verb;
|
||||
#[async_trait]
|
||||
impl UserVerb for Verb {
|
||||
async fn handle(self: &Self, ctx: &VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
|
||||
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
|
||||
let mut help = None;
|
||||
if !ctx.session_dat.less_explicit_mode {
|
||||
help = help.or_else(|| EXPLICIT_HELP_PAGES.get(remaining))
|
||||
|
@ -4,7 +4,7 @@ use async_trait::async_trait;
|
||||
pub struct Verb;
|
||||
#[async_trait]
|
||||
impl UserVerb for Verb {
|
||||
async fn handle(self: &Self, _ctx: &VerbContext, _verb: &str, _remaining: &str) -> UResult<()> {
|
||||
async fn handle(self: &Self, _ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
use super::{
|
||||
VerbContext, UserVerb, UserVerbRef, UResult
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub struct Verb;
|
||||
#[async_trait]
|
||||
impl UserVerb for Verb {
|
||||
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> {
|
||||
(*ctx.session_dat).less_explicit_mode = true;
|
||||
ctx.trans.save_session_model(ctx.session, ctx.session_dat).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
static VERB_INT: Verb = Verb;
|
||||
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -1,13 +1,17 @@
|
||||
use nom::{
|
||||
bytes::complete::{take_till1},
|
||||
character::complete::space0,
|
||||
bytes::complete::{take_till1, take_while},
|
||||
character::{complete::{space0, space1, alpha1}},
|
||||
combinator::{recognize, fail, eof},
|
||||
sequence::terminated,
|
||||
branch::alt,
|
||||
error::{context, VerboseError, VerboseErrorKind},
|
||||
IResult,
|
||||
};
|
||||
|
||||
pub fn parse_command_name(input: &str) -> (&str, &str) {
|
||||
fn parse(input: &str) -> IResult<&str, &str> {
|
||||
let (input, _) = space0(input)?;
|
||||
let (input, cmd) = take_till1(|c| c == ' ' || c == '\n')(input)?;
|
||||
let (input, cmd) = take_till1(|c| c == ' ' || c == '\t')(input)?;
|
||||
let (input, _) = space0(input)?;
|
||||
Ok((input, cmd))
|
||||
}
|
||||
@ -17,3 +21,101 @@ pub fn parse_command_name(input: &str) -> (&str, &str) {
|
||||
Ok((rest, command)) => (command, rest)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_username(input: &str) -> Result<(&str, &str), &'static str> {
|
||||
const CATCHALL_ERROR: &'static str = "Must only contain alphanumeric characters or _";
|
||||
fn parse_valid(input: &str) -> IResult<&str, (), VerboseError<&str>> {
|
||||
let (input, l1) = context("Must start with a letter", alpha1)(input)?;
|
||||
let (input, l2) = context(CATCHALL_ERROR,
|
||||
take_while(|c: char| c.is_alphanumeric() || c == '_'))(input)?;
|
||||
if l1.len() + l2.len() > 20 {
|
||||
context("Limit of 20 characters", fail::<&str, &str, VerboseError<&str>>)(input)?;
|
||||
}
|
||||
Ok((input, ()))
|
||||
}
|
||||
match terminated(recognize(parse_valid), alt((space1, eof)))(input) {
|
||||
Ok((input, username)) => Ok((username, input)),
|
||||
Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) =>
|
||||
Err(e.errors.into_iter().find_map(|k| match k.1 {
|
||||
VerboseErrorKind::Context(s) => Some(s),
|
||||
_ => None
|
||||
}).unwrap_or(CATCHALL_ERROR)),
|
||||
Err(_) => Err(CATCHALL_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_parses_normal_command() {
|
||||
assert_eq!(parse_command_name("help"),
|
||||
("help", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_normal_command_with_arg() {
|
||||
assert_eq!(parse_command_name("help \t testing stuff"),
|
||||
("help", "testing stuff"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_commands_with_leading_whitespace() {
|
||||
assert_eq!(parse_command_name(" \t \thelp \t testing stuff"),
|
||||
("help", "testing stuff"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_empty_command_names() {
|
||||
assert_eq!(parse_command_name(""),
|
||||
("", ""));
|
||||
assert_eq!(parse_command_name(" \t "),
|
||||
("", ""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_usernames() {
|
||||
assert_eq!(parse_username("Wizard123"), Ok(("Wizard123", "")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_usernames_with_further_args() {
|
||||
assert_eq!(parse_username("Wizard_123 with cat"), Ok(("Wizard_123", "with cat")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_parses_alpha_only_usernames() {
|
||||
assert_eq!(parse_username("W"), Ok(("W", "")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_fails_on_empty_usernames() {
|
||||
assert_eq!(parse_username(""), Err("Must start with a letter"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_fails_on_usernames_with_invalid_start() {
|
||||
assert_eq!(parse_username("#hack"), Err("Must start with a letter"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_fails_on_usernames_with_underscore_start() {
|
||||
assert_eq!(parse_username("_hack"), Err("Must start with a letter"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_fails_on_usernames_with_number_start() {
|
||||
assert_eq!(parse_username("31337 #"), Err("Must start with a letter"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_fails_on_usernames_with_bad_characters() {
|
||||
assert_eq!(parse_username("Wizard!"), Err("Must only contain alphanumeric characters or _"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_fails_on_long_usernames() {
|
||||
assert_eq!(parse_username("A23456789012345678901"), Err("Limit of 20 characters"));
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use ansi_macro::ansi;
|
||||
pub struct Verb;
|
||||
#[async_trait]
|
||||
impl UserVerb for Verb {
|
||||
async fn handle(self: &Self, ctx: &VerbContext, _verb: &str, _remaining: &str) -> UResult<()> {
|
||||
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, _remaining: &str) -> UResult<()> {
|
||||
ctx.trans.queue_for_session(ctx.session,
|
||||
Some(ansi!("<red>Bye!<reset>\r\n"))).await?;
|
||||
ctx.trans.queue_for_session(ctx.session, None).await?;
|
||||
|
18
blastmud_game/src/message_handler/user_commands/register.rs
Normal file
18
blastmud_game/src/message_handler/user_commands/register.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use super::{VerbContext, UserVerb, UserVerbRef, UResult};
|
||||
use async_trait::async_trait;
|
||||
use super::{user_error, parsing::parse_username};
|
||||
|
||||
pub struct Verb;
|
||||
#[async_trait]
|
||||
impl UserVerb for Verb {
|
||||
async fn handle(self: &Self, _ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
|
||||
let (username, password) = match parse_username(remaining) {
|
||||
Err(e) => user_error("Invalid username: ".to_owned() + e)?,
|
||||
Ok(r) => r
|
||||
};
|
||||
let _pwhash = bcrypt::hash(password, 10);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
static VERB_INT: Verb = Verb;
|
||||
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -1 +1,3 @@
|
||||
pub mod session;
|
||||
pub mod user;
|
||||
pub mod item;
|
||||
|
56
blastmud_game/src/models/item.rs
Normal file
56
blastmud_game/src/models/item.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::collections::BTreeMap;
|
||||
use super::user::{SkillType, StatType};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum BuffCause {
|
||||
WaitingTask { task_code: String, task_type: String },
|
||||
ByItem { item_code: String, item_type: String }
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum BuffImpact {
|
||||
ChangeStat { stat: StatType, magnitude: i16 },
|
||||
ChangeSkill { stat: StatType, magnitude: i16 }
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Buff {
|
||||
description: String,
|
||||
cause: BuffCause,
|
||||
impacts: Vec<BuffImpact>
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum Subattack {
|
||||
Normal,
|
||||
Powerattacking,
|
||||
Feinting,
|
||||
Grabbing,
|
||||
Wrestling
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum LocationActionType {
|
||||
Normal,
|
||||
Sitting,
|
||||
Reclining,
|
||||
Worn, // Clothing etc...
|
||||
Wielded,
|
||||
Attacking(Subattack),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Item {
|
||||
pub item_code: String,
|
||||
pub item_type: String,
|
||||
pub location: String, // Item reference as item_type/item_code.
|
||||
pub action_type: LocationActionType,
|
||||
pub presence_target: Option<String>, // e.g. what are they sitting on.
|
||||
pub is_static: bool,
|
||||
|
||||
pub total_xp: u64,
|
||||
pub total_stats: BTreeMap<StatType, u64>,
|
||||
pub total_skills: BTreeMap<SkillType, u64>,
|
||||
pub temporary_buffs: Vec<Buff>,
|
||||
}
|
119
blastmud_game/src/models/user.rs
Normal file
119
blastmud_game/src/models/user.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct UserTermData {
|
||||
pub accepted_terms: BTreeMap<String, DateTime<Utc>>,
|
||||
pub last_presented_term: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct UserExperienceData {
|
||||
pub spent_xp: u64, // Since last chargen complete.
|
||||
pub completed_journals: BTreeMap<String, DateTime<Utc>>,
|
||||
pub xp_change_for_this_reroll: i64,
|
||||
pub crafted_items: BTreeMap<String, u64>
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum SkillType {
|
||||
Apraise,
|
||||
Blades,
|
||||
Bombs,
|
||||
Chemistry,
|
||||
Climb,
|
||||
Clubs,
|
||||
Craft,
|
||||
Fish,
|
||||
Fists,
|
||||
Flails,
|
||||
Fuck,
|
||||
Hack,
|
||||
Locksmith,
|
||||
Medic,
|
||||
Persuade,
|
||||
Pilot,
|
||||
Pistols,
|
||||
Quickdraw,
|
||||
Repair,
|
||||
Ride,
|
||||
Rifles,
|
||||
Scavenge,
|
||||
Science,
|
||||
Sneak,
|
||||
Spears,
|
||||
Swim,
|
||||
Teach,
|
||||
Throw,
|
||||
Track,
|
||||
Wrestle,
|
||||
Whips
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum StatType {
|
||||
Brains,
|
||||
Senses,
|
||||
Brawn,
|
||||
Reflexes,
|
||||
Endurance,
|
||||
Cool
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct User {
|
||||
pub username: String,
|
||||
pub password_hash: String, // bcrypted.
|
||||
pub player_item_id: u64,
|
||||
pub registered_at: Option<DateTime<Utc>>,
|
||||
pub banned_until: Option<DateTime<Utc>>,
|
||||
pub abandoned_at: Option<DateTime<Utc>>,
|
||||
pub chargen_last_completed_at: Option<DateTime<Utc>>,
|
||||
|
||||
pub terms: UserTermData,
|
||||
pub experience: UserExperienceData,
|
||||
pub raw_skills: BTreeMap<SkillType, u16>,
|
||||
pub raw_stats: BTreeMap<StatType, u16>,
|
||||
// Reminder: Consider backwards compatibility when updating this. New fields should generally
|
||||
// be an Option, or things will crash out for existing sessions.
|
||||
}
|
||||
|
||||
impl Default for UserTermData {
|
||||
fn default() -> Self {
|
||||
UserTermData {
|
||||
accepted_terms: BTreeMap::new(),
|
||||
last_presented_term: None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for UserExperienceData {
|
||||
fn default() -> Self {
|
||||
UserExperienceData {
|
||||
spent_xp: 0,
|
||||
completed_journals: BTreeMap::new(),
|
||||
xp_change_for_this_reroll: 0,
|
||||
crafted_items: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for User {
|
||||
fn default() -> Self {
|
||||
User {
|
||||
username: "unknown".to_owned(),
|
||||
password_hash: "unknown".to_owned(),
|
||||
player_item_id: 0,
|
||||
registered_at: None,
|
||||
banned_until: None,
|
||||
abandoned_at: None,
|
||||
chargen_last_completed_at: None,
|
||||
|
||||
terms: UserTermData::default(),
|
||||
experience: UserExperienceData::default(),
|
||||
raw_skills: BTreeMap::new(),
|
||||
raw_stats: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
@ -18,15 +18,12 @@ CREATE TABLE sessions (
|
||||
CREATE INDEX session_by_listener ON sessions(listener);
|
||||
|
||||
CREATE TABLE items (
|
||||
item_id BIGINT NOT NULL PRIMARY KEY,
|
||||
item_code TEXT NOT NULL,
|
||||
item_type TEXT NOT NULL,
|
||||
location BIGINT REFERENCES items(item_id),
|
||||
details JSONB NOT NULL,
|
||||
UNIQUE (item_code, item_type)
|
||||
item_id BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
details JSONB NOT NULL
|
||||
);
|
||||
CREATE INDEX item_index ON items (item_code, item_type);
|
||||
CREATE INDEX item_by_loc ON items (location);
|
||||
CREATE UNIQUE INDEX item_index ON items ((details->>'item_code'), (details->>'item_type'));
|
||||
CREATE INDEX item_by_loc ON items ((details->>'location'));
|
||||
CREATE INDEX item_by_static ON items ((cast(details->>'is_static' as boolean)));
|
||||
|
||||
CREATE TABLE users (
|
||||
username TEXT NOT NULL PRIMARY KEY,
|
||||
@ -35,6 +32,7 @@ CREATE TABLE users (
|
||||
details JSONB NOT NULL
|
||||
);
|
||||
CREATE INDEX user_by_listener ON users(current_listener);
|
||||
CREATE INDEX user_by_session ON users(current_session);
|
||||
|
||||
CREATE UNLOGGED TABLE sendqueue (
|
||||
item BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
@ -44,11 +42,9 @@ CREATE UNLOGGED TABLE sendqueue (
|
||||
);
|
||||
|
||||
CREATE TABLE tasks (
|
||||
task_code TEXT NOT NULL,
|
||||
task_type TEXT NOT NULL,
|
||||
is_static BOOL NOT NULL,
|
||||
next_scheduled TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
details JSONB NOT NULL,
|
||||
PRIMARY KEY (task_code, task_type)
|
||||
task_id BIGSERIAL NOT NULL PRIMARY KEY,
|
||||
details JSONB NOT NULL
|
||||
);
|
||||
CREATE INDEX task_by_next_scheduled ON tasks(next_scheduled);
|
||||
CREATE UNIQUE INDEX tasks_by_code_type ON tasks((details->>'task_code'), (details->>'task_type'));
|
||||
CREATE INDEX tasks_by_static ON tasks((cast(details->>'is_static' as boolean)));
|
||||
CREATE INDEX tasks_by_scheduled ON tasks((details->>'next_scheduled'));
|
||||
|
Loading…
Reference in New Issue
Block a user