feat: bugfixes
This commit is contained in:
parent
74701aa0c5
commit
bf07ba9fa2
39
Cargo.lock
generated
39
Cargo.lock
generated
@ -211,6 +211,15 @@ version = "0.1.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.28"
|
version = "0.3.28"
|
||||||
@ -310,6 +319,16 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
@ -907,9 +926,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toad"
|
name = "toad"
|
||||||
version = "1.0.0-beta.6"
|
version = "1.0.0-beta.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b305d4763b0236558486735374e796a028567a8d6f6a4c94c07096b73573ba0a"
|
checksum = "6b4d56ca31b3b83e311136e8e425dc62b8190ed72e2677485431a419493828f2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"embedded-time",
|
"embedded-time",
|
||||||
"log",
|
"log",
|
||||||
@ -1001,9 +1020,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toad-msg"
|
name = "toad-msg"
|
||||||
version = "1.0.0-beta.2"
|
version = "1.0.0-beta.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63f5ff0cb4b95ec5eb83db517a2301d6a5a5917d94124140dff2fafa283a41f5"
|
checksum = "219f76dce7b054dc71ff7dff5409b450f821b2bdeccf67d95d3577aa029fe737"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"blake2",
|
"blake2",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
@ -1013,6 +1032,7 @@ dependencies = [
|
|||||||
"toad-len",
|
"toad-len",
|
||||||
"toad-macros",
|
"toad-macros",
|
||||||
"toad-map",
|
"toad-map",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1141,6 +1161,17 @@ dependencies = [
|
|||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
@ -5,8 +5,8 @@ edition = "2021"
|
|||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
toad = "1.0.0-beta.6"
|
toad = "1.0.0-beta.8"
|
||||||
toad-msg = "1.0.0-beta.2"
|
toad-msg = "1.0.0-beta.5"
|
||||||
simple_logger = "4.2"
|
simple_logger = "4.2"
|
||||||
nb = "1.1.0"
|
nb = "1.1.0"
|
||||||
serde = {version = "1", features = ["derive"]}
|
serde = {version = "1", features = ["derive"]}
|
||||||
|
21
src/app.rs
21
src/app.rs
@ -1,3 +1,6 @@
|
|||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use crate::env::Env;
|
||||||
use crate::model::{GroupRepoImpl,
|
use crate::model::{GroupRepoImpl,
|
||||||
HashedTextExt,
|
HashedTextExt,
|
||||||
HashedTextExtImpl,
|
HashedTextExtImpl,
|
||||||
@ -15,10 +18,14 @@ pub trait App: Send + Sync + Sized {
|
|||||||
type HashedTextExt: HashedTextExt;
|
type HashedTextExt: HashedTextExt;
|
||||||
type UserSessionExt: UserSessionExt;
|
type UserSessionExt: UserSessionExt;
|
||||||
|
|
||||||
|
fn env(&self) -> &Env;
|
||||||
fn db(&self) -> &Self::Db;
|
fn db(&self) -> &Self::Db;
|
||||||
fn hashed_text(&self) -> &Self::HashedTextExt;
|
fn hashed_text(&self) -> &Self::HashedTextExt;
|
||||||
fn user_session(&self) -> &Self::UserSessionExt;
|
fn user_session(&self) -> &Self::UserSessionExt;
|
||||||
fn user(&self) -> &Self::UserRepo;
|
fn user(&self) -> &Self::UserRepo;
|
||||||
|
|
||||||
|
fn enqueue_shutdown(&self);
|
||||||
|
fn should_shutdown(&self) -> bool;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AppConcrete {
|
pub struct AppConcrete {
|
||||||
@ -27,6 +34,8 @@ pub struct AppConcrete {
|
|||||||
pub user_session: &'static UserSessionExtImpl<PostgresImpl<postgres::Client>,
|
pub user_session: &'static UserSessionExtImpl<PostgresImpl<postgres::Client>,
|
||||||
GroupRepoImpl<PostgresImpl<postgres::Client>>>,
|
GroupRepoImpl<PostgresImpl<postgres::Client>>>,
|
||||||
pub user: &'static UserRepoImpl<PostgresImpl<postgres::Client>>,
|
pub user: &'static UserRepoImpl<PostgresImpl<postgres::Client>>,
|
||||||
|
pub env: Env,
|
||||||
|
pub should_shutdown: Mutex<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App for AppConcrete {
|
impl App for AppConcrete {
|
||||||
@ -37,6 +46,10 @@ impl App for AppConcrete {
|
|||||||
type HashedTextExt = HashedTextExtImpl<Self::Db>;
|
type HashedTextExt = HashedTextExtImpl<Self::Db>;
|
||||||
type UserSessionExt = UserSessionExtImpl<Self::Db, GroupRepoImpl<Self::Db>>;
|
type UserSessionExt = UserSessionExtImpl<Self::Db, GroupRepoImpl<Self::Db>>;
|
||||||
|
|
||||||
|
fn env(&self) -> &Env {
|
||||||
|
&self.env
|
||||||
|
}
|
||||||
|
|
||||||
fn db(&self) -> &Self::Db {
|
fn db(&self) -> &Self::Db {
|
||||||
self.pg
|
self.pg
|
||||||
}
|
}
|
||||||
@ -52,4 +65,12 @@ impl App for AppConcrete {
|
|||||||
fn user(&self) -> &Self::UserRepo {
|
fn user(&self) -> &Self::UserRepo {
|
||||||
self.user
|
self.user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn enqueue_shutdown(&self) {
|
||||||
|
*self.should_shutdown.lock().unwrap() = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_shutdown(&self) -> bool {
|
||||||
|
*self.should_shutdown.lock().unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
18
src/env.rs
18
src/env.rs
@ -1,10 +1,15 @@
|
|||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::net::{AddrParseError, SocketAddr};
|
use std::net::{AddrParseError, SocketAddr};
|
||||||
use std::num::ParseIntError;
|
use std::num::ParseIntError;
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use naan::prelude::*;
|
use naan::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
pub enum Environment {
|
||||||
|
Debug,
|
||||||
|
Release,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct Api {
|
pub struct Api {
|
||||||
pub addr: SocketAddr,
|
pub addr: SocketAddr,
|
||||||
@ -23,6 +28,7 @@ pub struct Postgres {
|
|||||||
pub struct Env {
|
pub struct Env {
|
||||||
pub postgres: Postgres,
|
pub postgres: Postgres,
|
||||||
pub api: Api,
|
pub api: Api,
|
||||||
|
pub environ: Environment,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -66,6 +72,14 @@ impl Env {
|
|||||||
.parse()
|
.parse()
|
||||||
.map_err(|e| Error::VarNotSocketAddr("API_ADDR".into(), api_addr, e))? };
|
.map_err(|e| Error::VarNotSocketAddr("API_ADDR".into(), api_addr, e))? };
|
||||||
|
|
||||||
Ok(Env { postgres, api })
|
Ok(Env { postgres,
|
||||||
|
api,
|
||||||
|
environ: match get("ENVIRON")?.unwrap_or("release".into())
|
||||||
|
.to_lowercase()
|
||||||
|
.as_str()
|
||||||
|
{
|
||||||
|
| "debug" => Environment::Debug,
|
||||||
|
| _ => Environment::Release,
|
||||||
|
} })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
39
src/main.rs
39
src/main.rs
@ -1,5 +1,6 @@
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::ptr::hash;
|
use std::ptr::hash;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use app::{App, AppConcrete};
|
use app::{App, AppConcrete};
|
||||||
use err::ToE;
|
use err::ToE;
|
||||||
@ -20,6 +21,7 @@ use toad::platform::Platform as _;
|
|||||||
use toad::req::Req;
|
use toad::req::Req;
|
||||||
use toad::resp::{code, Resp};
|
use toad::resp::{code, Resp};
|
||||||
use toad_msg::alloc::Message;
|
use toad_msg::alloc::Message;
|
||||||
|
use toad_msg::repeat::PATH;
|
||||||
use toad_msg::{Code, ContentFormat, MessageBuilder, MessageOptions, Type};
|
use toad_msg::{Code, ContentFormat, MessageBuilder, MessageOptions, Type};
|
||||||
|
|
||||||
use crate::err::E;
|
use crate::err::E;
|
||||||
@ -138,15 +140,22 @@ fn handle_request<A>(app: &A, req: Addrd<Req<ToadT>>) -> Addrd<Message>
|
|||||||
{
|
{
|
||||||
let body = || {
|
let body = || {
|
||||||
let routes: Vec<fn(&A, &Actor, Addrd<&Message>) -> Result<Option<Message>, E>> =
|
let routes: Vec<fn(&A, &Actor, Addrd<&Message>) -> Result<Option<Message>, E>> =
|
||||||
vec![route::users::users, route::user_sessions::user_sessions];
|
vec![route::users::users,
|
||||||
|
route::user_sessions::user_sessions,
|
||||||
|
route::debug::debug];
|
||||||
|
|
||||||
let actor = Ok(req.data().payload()).and_then(serde_json::from_slice::<Option<ReqPayload<()>>>)
|
let actor =
|
||||||
.map_err(ToE::to_e)?
|
Ok(Some(req.data().payload())).map(|p| p.filter(|p| !p.is_empty()))
|
||||||
.and_then(|r| r.session)
|
.and_then(|p| {
|
||||||
.map(|s| app.user_session().touch(UserSession::from(s)))
|
p.map(serde_json::from_slice::<ReqPayload<()>>)
|
||||||
.sequence::<hkt::ResultOk<_>>()
|
.sequence::<hkt::ResultOk<_>>()
|
||||||
.map_err(ToE::to_e)?
|
})
|
||||||
.unwrap_or_default();
|
.map_err(ToE::to_e)?
|
||||||
|
.and_then(|r| r.session)
|
||||||
|
.map(|s| app.user_session().touch(UserSession::from(s)))
|
||||||
|
.sequence::<hkt::ResultOk<_>>()
|
||||||
|
.map_err(ToE::to_e)?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let not_found = || {
|
let not_found = || {
|
||||||
Message::builder(Type::Ack, toad::resp::code::NOT_FOUND).token(req.data().msg().token)
|
Message::builder(Type::Ack, toad::resp::code::NOT_FOUND).token(req.data().msg().token)
|
||||||
@ -171,7 +180,8 @@ fn handle_request<A>(app: &A, req: Addrd<Req<ToadT>>) -> Addrd<Message>
|
|||||||
e
|
e
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
Addrd(Message::builder(Type::Ack, e.code).content_format(ContentFormat::Json)
|
Addrd(Message::builder(Type::Ack, e.code).token(req.data().msg().token)
|
||||||
|
.content_format(ContentFormat::Json)
|
||||||
.payload(serde_json::to_vec(&e).unwrap())
|
.payload(serde_json::to_vec(&e).unwrap())
|
||||||
.build(),
|
.build(),
|
||||||
req.addr())
|
req.addr())
|
||||||
@ -182,6 +192,11 @@ fn server_worker<A>(app: &A, p: &'static Toad)
|
|||||||
where A: App
|
where A: App
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
|
if app.should_shutdown() {
|
||||||
|
log::info!("shutting down");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
match nb::block!(p.poll_req()) {
|
match nb::block!(p.poll_req()) {
|
||||||
| Err(e) => log::error!("{e:?}"),
|
| Err(e) => log::error!("{e:?}"),
|
||||||
| Ok(req) => {
|
| Ok(req) => {
|
||||||
@ -200,7 +215,7 @@ fn main() {
|
|||||||
unsafe { core::mem::transmute::<&T, &'static T>(t) }
|
unsafe { core::mem::transmute::<&T, &'static T>(t) }
|
||||||
}
|
}
|
||||||
|
|
||||||
simple_logger::init().unwrap();
|
simple_logger::init_with_level(log::Level::Debug).unwrap();
|
||||||
|
|
||||||
let env = env::Env::try_read().unwrap();
|
let env = env::Env::try_read().unwrap();
|
||||||
|
|
||||||
@ -234,7 +249,9 @@ fn main() {
|
|||||||
let app = AppConcrete { pg,
|
let app = AppConcrete { pg,
|
||||||
hashed_text,
|
hashed_text,
|
||||||
user_session,
|
user_session,
|
||||||
user };
|
user,
|
||||||
|
env,
|
||||||
|
should_shutdown: Mutex::new(false) };
|
||||||
|
|
||||||
server_worker(&app, toad);
|
server_worker(&app, toad);
|
||||||
}
|
}
|
||||||
|
@ -52,14 +52,15 @@ impl UnmarshalRow for User {
|
|||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct UserPatch {
|
pub struct UserPatch {
|
||||||
pub tag: Option<UserTag>,
|
pub tag: Option<String>,
|
||||||
|
pub tag_discrim: Option<u32>,
|
||||||
pub password: Option<HashedText>,
|
pub password: Option<HashedText>,
|
||||||
pub email: Option<Email>,
|
pub email: Option<Email>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct UserInsert {
|
pub struct UserInsert {
|
||||||
pub tag: UserTag,
|
pub tag: String,
|
||||||
pub password: HashedText,
|
pub password: HashedText,
|
||||||
pub email: Email,
|
pub email: Email,
|
||||||
}
|
}
|
||||||
@ -112,9 +113,9 @@ impl<Db> ReadOne for UserRepoImpl<Db> where Db: Postgres + 'static
|
|||||||
use UserRepoError::*;
|
use UserRepoError::*;
|
||||||
|
|
||||||
static QUERY_LINES: [&'static str; 3] =
|
static QUERY_LINES: [&'static str; 3] =
|
||||||
["select uid, tag, email",
|
["select uid, tag, discrim, email",
|
||||||
"from public.usr",
|
"from public.usr",
|
||||||
"where uid = human_uuid.huid_of_string($1) and deleted = false"];
|
"where uid = (($1 :: text) :: human_uuid.huid) and deleted = false"];
|
||||||
|
|
||||||
let paths = vec![format!("/users/{id}/tag"), format!("/users/{id}/email")].into_iter()
|
let paths = vec![format!("/users/{id}/tag"), format!("/users/{id}/email")].into_iter()
|
||||||
.map(Path::parse)
|
.map(Path::parse)
|
||||||
@ -154,20 +155,25 @@ impl<Db> ReadMany for UserRepoImpl<Db> where Db: Postgres + 'static
|
|||||||
return Err(PageInvalid);
|
return Err(PageInvalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
static QUERY_AFTER_LINES: [&'static str; 6] =
|
static QUERY_AFTER_LINES: [&'static str; 9] =
|
||||||
["with after as (select id from public.usr where uid = human_uuid.huid_of_string($1))",
|
["with after as (select id from public.usr where uid = (($1 :: text) :: human_uuid.huid))",
|
||||||
"select human_uuid.huid_to_string(uid), tag, email",
|
"select uid :: text",
|
||||||
|
" , tag :: text",
|
||||||
|
" , discrim",
|
||||||
|
" , email",
|
||||||
"from public.usr",
|
"from public.usr",
|
||||||
"where id > after.id and deleted = false",
|
"where id > after.id and deleted = false",
|
||||||
"order by id asc",
|
"order by id asc",
|
||||||
"limit $2"];
|
"limit $2"];
|
||||||
|
|
||||||
static QUERY_FIRST_LINES: [&'static str; 5] =
|
static QUERY_FIRST_LINES: [&'static str; 8] = ["select uid :: text",
|
||||||
["select human_uuid.huid_to_string(uid), tag, email",
|
" , tag :: text",
|
||||||
"from public.usr",
|
" , discrim",
|
||||||
"where id > after.id and deleted = false",
|
" , email",
|
||||||
"order by id asc",
|
"from public.usr",
|
||||||
"limit $1"];
|
"deleted = false",
|
||||||
|
"order by id asc",
|
||||||
|
"limit $1"];
|
||||||
|
|
||||||
let usrs: Vec<User> =
|
let usrs: Vec<User> =
|
||||||
self.0
|
self.0
|
||||||
@ -216,7 +222,7 @@ impl<Db> Patch for UserRepoImpl<Db> where Db: Postgres + 'static
|
|||||||
" , password = coalesce($3, password)",
|
" , password = coalesce($3, password)",
|
||||||
" , email = coalesce($4, email)",
|
" , email = coalesce($4, email)",
|
||||||
"from public.usr",
|
"from public.usr",
|
||||||
"where uid = human_uuid.huid_of_string($1) and deleted = false"];
|
"where uid = (($1 :: text) :: human_uuid.huid) and deleted = false"];
|
||||||
|
|
||||||
let paths =
|
let paths =
|
||||||
vec![Some("/users/{id}/tag").filter(|_| patch.tag.is_some()),
|
vec![Some("/users/{id}/tag").filter(|_| patch.tag.is_some()),
|
||||||
@ -237,7 +243,7 @@ impl<Db> Patch for UserRepoImpl<Db> where Db: Postgres + 'static
|
|||||||
c.execute(&QUERY_LINES.iter()
|
c.execute(&QUERY_LINES.iter()
|
||||||
.fold(String::new(), |b, a| format!("{b}{a}\n")),
|
.fold(String::new(), |b, a| format!("{b}{a}\n")),
|
||||||
&[&id.as_ref(),
|
&[&id.as_ref(),
|
||||||
&patch.tag.as_ref().map(|t| &t.tag),
|
&patch.tag.as_ref(),
|
||||||
&patch.password.as_ref().map(|h| h.as_ref()),
|
&patch.password.as_ref().map(|h| h.as_ref()),
|
||||||
&patch.email.as_ref().map(|e| e.as_ref())])
|
&patch.email.as_ref().map(|e| e.as_ref())])
|
||||||
})
|
})
|
||||||
@ -256,8 +262,8 @@ impl<Db> Insert for UserRepoImpl<Db> where Db: Postgres + 'static
|
|||||||
static QUERY_LINES: [&'static str; 5] = ["insert into public.usr",
|
static QUERY_LINES: [&'static str; 5] = ["insert into public.usr",
|
||||||
" (tag, password, email)",
|
" (tag, password, email)",
|
||||||
"values",
|
"values",
|
||||||
" ($2, $3, $4)",
|
" ($1 :: text, $2 :: text, $3 :: text)",
|
||||||
"returning human_uuid.huid_to_string(uid);"];
|
"returning (uid :: text);"];
|
||||||
|
|
||||||
if !self.0
|
if !self.0
|
||||||
.authorized(&Path::parse("/users/"), Mode::Write, actor)
|
.authorized(&Path::parse("/users/"), Mode::Write, actor)
|
||||||
@ -270,7 +276,7 @@ impl<Db> Insert for UserRepoImpl<Db> where Db: Postgres + 'static
|
|||||||
.with_client(|c| {
|
.with_client(|c| {
|
||||||
c.query_one(&QUERY_LINES.iter()
|
c.query_one(&QUERY_LINES.iter()
|
||||||
.fold(String::new(), |b, a| format!("{b}{a}\n")),
|
.fold(String::new(), |b, a| format!("{b}{a}\n")),
|
||||||
&[&insert.tag.tag,
|
&[&insert.tag,
|
||||||
&insert.password.as_ref(),
|
&insert.password.as_ref(),
|
||||||
&insert.email.as_ref()])
|
&insert.email.as_ref()])
|
||||||
})
|
})
|
||||||
@ -459,20 +465,17 @@ mod tests {
|
|||||||
let repo = UserRepoImpl(unsafe { std::mem::transmute::<_, &'static Postgres<()>>(&db) });
|
let repo = UserRepoImpl(unsafe { std::mem::transmute::<_, &'static Postgres<()>>(&db) });
|
||||||
|
|
||||||
assert!(repo.insert(&Actor::default(),
|
assert!(repo.insert(&Actor::default(),
|
||||||
UserInsert { tag: UserTag { tag: "foo".into(),
|
UserInsert { tag: "foo".into(),
|
||||||
discrim: 0 },
|
|
||||||
password: HashedText::from("poop"),
|
password: HashedText::from("poop"),
|
||||||
email: Email::from("foo@bar.baz") })
|
email: Email::from("foo@bar.baz") })
|
||||||
.is_ok());
|
.is_ok());
|
||||||
assert!(repo.insert(&Actor::default(),
|
assert!(repo.insert(&Actor::default(),
|
||||||
UserInsert { tag: UserTag { tag: "foo".into(),
|
UserInsert { tag: "foo".into(),
|
||||||
discrim: 0 },
|
|
||||||
password: HashedText::from("poop"),
|
password: HashedText::from("poop"),
|
||||||
email: Email::from("foo@bar.baz") })
|
email: Email::from("foo@bar.baz") })
|
||||||
.is_err());
|
.is_err());
|
||||||
assert!(repo.insert(&Actor::default(),
|
assert!(repo.insert(&Actor::default(),
|
||||||
UserInsert { tag: UserTag { tag: "bar".into(),
|
UserInsert { tag: "bar".into(),
|
||||||
discrim: 0 },
|
|
||||||
password: HashedText::from("poop"),
|
password: HashedText::from("poop"),
|
||||||
email: Email::from("bar@bar.baz") })
|
email: Email::from("bar@bar.baz") })
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
@ -80,8 +80,18 @@ pub trait Postgres
|
|||||||
where F: FnOnce(&mut Self::Client) -> Result<R, DbError<Self>>;
|
where F: FnOnce(&mut Self::Client) -> Result<R, DbError<Self>>;
|
||||||
|
|
||||||
fn authorized(&self, path: &Path, wants_to: Mode, actor: &Actor) -> Result<bool, DbError<Self>> {
|
fn authorized(&self, path: &Path, wants_to: Mode, actor: &Actor) -> Result<bool, DbError<Self>> {
|
||||||
const Q: &'static str = "select * from public.perm where path = $1";
|
let q = vec!["select p.owner_user_mode :: text",
|
||||||
let got_perm = self.with_client(|c| c.query_opt(Q, &[&path.to_string()]))?;
|
" , p.owner_group_mode :: text",
|
||||||
|
" , p.everyone_mode :: text",
|
||||||
|
" , p.path",
|
||||||
|
" , ou.uid :: text as owner_user",
|
||||||
|
" , og.uid :: text as owner_group",
|
||||||
|
"from public.perm p",
|
||||||
|
"inner join public.grp og on og.id = p.owner_group",
|
||||||
|
"inner join public.usr ou on ou.id = p.owner_user",
|
||||||
|
"where path = $1 :: text",].into_iter()
|
||||||
|
.fold(String::new(), |b, a| format!("{b}\n{a}"));
|
||||||
|
let got_perm = self.with_client(|c| c.query_opt(&q, &[&path.to_string()]))?;
|
||||||
Ok(match got_perm {
|
Ok(match got_perm {
|
||||||
| None => false,
|
| None => false,
|
||||||
| Some(perm) => Perm::unmarshal(&perm)?.actor_can(actor, wants_to),
|
| Some(perm) => Perm::unmarshal(&perm)?.actor_can(actor, wants_to),
|
||||||
@ -106,7 +116,18 @@ pub trait Postgres
|
|||||||
format!("{b}, {a}")
|
format!("{b}, {a}")
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let q = format!("select * from public.perm where path in {set}");
|
|
||||||
|
let q = vec!["select p.owner_user_mode :: text",
|
||||||
|
" , p.owner_group_mode :: text",
|
||||||
|
" , p.everyone_mode :: text",
|
||||||
|
" , p.path",
|
||||||
|
" , ou.uid as owner_user",
|
||||||
|
" , og.uid as owner_group",
|
||||||
|
"from public.perm p",
|
||||||
|
"inner join public.grp og on og.id = p.owner_group",
|
||||||
|
"inner join public.usr ou on ou.id = p.owner_user",
|
||||||
|
"where path in {set}",].into_iter()
|
||||||
|
.fold(String::new(), |b, a| format!("{b}\n{a}"));
|
||||||
|
|
||||||
let pass = self.with_client(|c| c.query(&q, &[]))?
|
let pass = self.with_client(|c| c.query(&q, &[]))?
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||||
pub struct RepPayload<T> {
|
pub struct RepPayload<T, L> {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub t: T,
|
pub t: T,
|
||||||
#[serde(skip_serializing_if = "HashMap::is_empty")]
|
pub links: L,
|
||||||
pub links: HashMap<String, String>,
|
|
||||||
}
|
}
|
||||||
|
32
src/route/debug.rs
Normal file
32
src/route/debug.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use toad::net::Addrd;
|
||||||
|
use toad::resp::code::CREATED;
|
||||||
|
use toad_msg::alloc::Message;
|
||||||
|
use toad_msg::{Code, ContentFormat, MessageOptions, Type};
|
||||||
|
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::env::Environment;
|
||||||
|
use crate::err::{ToE, E};
|
||||||
|
use crate::model::{Actor, LoginError, LoginPayload, UserSessionExt};
|
||||||
|
|
||||||
|
pub fn debug<A>(app: &A, _: &Actor, req: Addrd<&Message>) -> Result<Option<Message>, E>
|
||||||
|
where A: App
|
||||||
|
{
|
||||||
|
let path_segments: Vec<&str> = req.data().path_segments().map_err(ToE::to_e)?;
|
||||||
|
|
||||||
|
Ok(Some(req.data().code)).map(|o| {
|
||||||
|
o.filter(|_| {
|
||||||
|
app.env().environ == Environment::Debug
|
||||||
|
&& path_segments.get(0) == Some(&"debug")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.and_then(|code| match (path_segments.get(1), code) {
|
||||||
|
| (Some(&"shutdown"), Some(Code::POST)) => {
|
||||||
|
log::info!("received shutdown request");
|
||||||
|
app.enqueue_shutdown();
|
||||||
|
Ok(Some(Message::builder(Type::Ack, CREATED).token(req.data()
|
||||||
|
.token)
|
||||||
|
.build()))
|
||||||
|
},
|
||||||
|
| _ => Ok(None),
|
||||||
|
})
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
|
pub mod debug;
|
||||||
pub mod user_sessions;
|
pub mod user_sessions;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
@ -7,29 +7,30 @@ use crate::app::App;
|
|||||||
use crate::err::{ToE, E};
|
use crate::err::{ToE, E};
|
||||||
use crate::model::{Actor, LoginError, LoginPayload, UserSessionExt};
|
use crate::model::{Actor, LoginError, LoginPayload, UserSessionExt};
|
||||||
|
|
||||||
pub fn user_sessions<A>(app: &A, actor: &Actor, req: Addrd<&Message>) -> Result<Option<Message>, E>
|
pub fn user_sessions<A>(app: &A, _: &Actor, req: Addrd<&Message>) -> Result<Option<Message>, E>
|
||||||
where A: App
|
where A: App
|
||||||
{
|
{
|
||||||
let path_segments: Vec<&str> = req.data().path().map_err(ToE::to_e)?;
|
let path_segments: Vec<&str> = req.data().path_segments().map_err(ToE::to_e)?;
|
||||||
|
|
||||||
Ok(None).map(|o| o.filter(|_: &()| path_segments.get(0) == Some(&"user_sessions")))
|
Ok(Some(req.data().code)).map(|o| o.filter(|_| path_segments.get(0) == Some(&"user_sessions")))
|
||||||
.and_then(|_| match req.data().code {
|
.and_then(|code| match code {
|
||||||
| Code::POST => {
|
| Some(Code::POST) => {
|
||||||
let p =
|
let p =
|
||||||
Some(req.data().payload.as_bytes()).filter(|b| !b.is_empty())
|
Some(req.data().payload.as_bytes()).filter(|b| !b.is_empty())
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
LoginError::<()>::MalformedPayload.into_e()
|
LoginError::<()>::MalformedPayload.into_e()
|
||||||
})?;
|
})?;
|
||||||
let p = serde_json::from_slice(p).map_err(ToE::to_e)?;
|
let p = serde_json::from_slice(p).map_err(ToE::to_e)?;
|
||||||
let session = app.user_session().login(p).map_err(LoginError::into_e)?;
|
let session =
|
||||||
let bytes =
|
app.user_session().login(p).map_err(LoginError::into_e)?;
|
||||||
|
let bytes =
|
||||||
serde_json::to_vec(&serde_json::json!({ "session": session })).map_err(ToE::to_e)?;
|
serde_json::to_vec(&serde_json::json!({ "session": session })).map_err(ToE::to_e)?;
|
||||||
|
|
||||||
Ok(Some(Message::builder(Type::Ack, CREATED).token(req.data().token)
|
Ok(Some(Message::builder(Type::Ack, CREATED).token(req.data().token)
|
||||||
.payload(bytes)
|
.payload(bytes)
|
||||||
.content_format(ContentFormat::Json)
|
.content_format(ContentFormat::Json)
|
||||||
.build()))
|
.build()))
|
||||||
},
|
},
|
||||||
| _ => Ok(None),
|
| _ => Ok(None),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -13,20 +13,35 @@ use crate::rep_payload::RepPayload;
|
|||||||
use crate::repo::{Del, Insert, Page, Patch, ReadMany, ReadOne};
|
use crate::repo::{Del, Insert, Page, Patch, ReadMany, ReadOne};
|
||||||
use crate::req_payload::ReqPayload;
|
use crate::req_payload::ReqPayload;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
|
pub struct SingleUserLinks {
|
||||||
|
groups: String,
|
||||||
|
login: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a UserId> for SingleUserLinks {
|
||||||
|
fn from(uid: &'a UserId) -> Self {
|
||||||
|
Self { groups: format!("groups?filter[user]={uid}"),
|
||||||
|
login: "user_sessions".to_string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn users<A>(app: &A, actor: &Actor, req: Addrd<&Message>) -> Result<Option<Message>, E>
|
pub fn users<A>(app: &A, actor: &Actor, req: Addrd<&Message>) -> Result<Option<Message>, E>
|
||||||
where A: App
|
where A: App
|
||||||
{
|
{
|
||||||
let path_segments: Vec<&str> = req.data().path().map_err(ToE::to_e)?;
|
let path_segments: Vec<&str> = req.data().path_segments().map_err(ToE::to_e)?;
|
||||||
|
|
||||||
Ok(None).map(|o| o.filter(|_: &()| path_segments.get(0) == Some(&"users")))
|
Ok(Some(req.data().code)).map(|o| o.filter(|_| path_segments.get(0) == Some(&"users")))
|
||||||
.and_then(|_| match (req.data().code, path_segments.get(1).map(|id| UserId::from(*id))) {
|
.and_then(|code| {
|
||||||
| (Code::GET, Some(uid)) => get_user(app, actor, uid, req),
|
match (code, path_segments.get(1).map(|id| UserId::from(*id))) {
|
||||||
| (Code::GET, None) => get_users(app, actor, req),
|
| (Some(Code::GET), Some(uid)) => get_user(app, actor, uid, req),
|
||||||
| (Code::PUT, Some(uid)) => put_user(app, actor, uid, req),
|
| (Some(Code::GET), None) => get_users(app, actor, req),
|
||||||
| (Code::POST, None) => post_user(app, actor, req),
|
| (Some(Code::PUT), Some(uid)) => put_user(app, actor, uid, req),
|
||||||
| (Code::DELETE, Some(uid)) => del_user(app, actor, uid, req),
|
| (Some(Code::POST), None) => post_user(app, actor, req),
|
||||||
| _ => Ok(None),
|
| (Some(Code::DELETE), Some(uid)) => del_user(app, actor, uid, req),
|
||||||
})
|
| _ => Ok(None),
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_user<A>(app: &A,
|
pub fn get_user<A>(app: &A,
|
||||||
@ -37,13 +52,19 @@ pub fn get_user<A>(app: &A,
|
|||||||
where A: App
|
where A: App
|
||||||
{
|
{
|
||||||
let user = app.user().get(&actor, &id).map_err(UserRepoError::into_e)?;
|
let user = app.user().get(&actor, &id).map_err(UserRepoError::into_e)?;
|
||||||
let rep = RepPayload { t: user,
|
|
||||||
links: HashMap::from([("login".into(), "user_sessions/".into())]) };
|
|
||||||
|
|
||||||
Ok(Some(Message::builder(Type::Ack, CONTENT).token(req.data().token)
|
user.map(|u| RepPayload { links: SingleUserLinks::from(&u.uid),
|
||||||
.content_format(ContentFormat::Json)
|
t: u })
|
||||||
.payload(serde_json::to_vec(&rep).map_err(ToE::to_e)?)
|
.map(|r| serde_json::to_vec(&r).map_err(ToE::to_e))
|
||||||
.build()))
|
.sequence::<hkt::ResultOk<_>>()
|
||||||
|
.map(|o| {
|
||||||
|
o.map(|r| {
|
||||||
|
Message::builder(Type::Ack, CONTENT).token(req.data().token)
|
||||||
|
.content_format(ContentFormat::Json)
|
||||||
|
.payload(r)
|
||||||
|
.build()
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_users<A>(app: &A, actor: &Actor, req: Addrd<&Message>) -> Result<Option<Message>, E>
|
pub fn get_users<A>(app: &A, actor: &Actor, req: Addrd<&Message>) -> Result<Option<Message>, E>
|
||||||
@ -71,6 +92,11 @@ pub fn get_users<A>(app: &A, actor: &Actor, req: Addrd<&Message>) -> Result<Opti
|
|||||||
pub fn post_user<A>(app: &A, actor: &Actor, req: Addrd<&Message>) -> Result<Option<Message>, E>
|
pub fn post_user<A>(app: &A, actor: &Actor, req: Addrd<&Message>) -> Result<Option<Message>, E>
|
||||||
where A: App
|
where A: App
|
||||||
{
|
{
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone)]
|
||||||
|
struct Rep {
|
||||||
|
uid: UserId,
|
||||||
|
}
|
||||||
|
|
||||||
let insert_required = || {
|
let insert_required = || {
|
||||||
E::new().code(BAD_REQUEST)
|
E::new().code(BAD_REQUEST)
|
||||||
.error("bad_request".into())
|
.error("bad_request".into())
|
||||||
@ -90,7 +116,9 @@ pub fn post_user<A>(app: &A, actor: &Actor, req: Addrd<&Message>) -> Result<Opti
|
|||||||
.insert(&actor, payload.t)
|
.insert(&actor, payload.t)
|
||||||
.map_err(UserRepoError::into_e)?;
|
.map_err(UserRepoError::into_e)?;
|
||||||
|
|
||||||
let bytes = serde_json::to_vec(&serde_json::json!({"uid": uid.to_string()})).map_err(ToE::to_e)?;
|
let rep = RepPayload { links: SingleUserLinks::from(&uid),
|
||||||
|
t: Rep { uid } };
|
||||||
|
let bytes = serde_json::to_vec(&rep).map_err(ToE::to_e)?;
|
||||||
|
|
||||||
Ok(Some(Message::builder(Type::Ack, CREATED).token(req.data().token)
|
Ok(Some(Message::builder(Type::Ack, CREATED).token(req.data().token)
|
||||||
.content_format(ContentFormat::Json)
|
.content_format(ContentFormat::Json)
|
||||||
|
Loading…
Reference in New Issue
Block a user