More tests, documentation and prepare to add other payment methods
This commit is contained in:
parent
4bc914b6df
commit
69bf0b5dff
30
Cargo.lock
generated
30
Cargo.lock
generated
@ -559,9 +559,9 @@ checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.53"
|
version = "0.1.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
|
checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -1183,6 +1183,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"actix 0.13.0",
|
"actix 0.13.0",
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
|
"async-trait",
|
||||||
"chrono",
|
"chrono",
|
||||||
"config",
|
"config",
|
||||||
"fake",
|
"fake",
|
||||||
@ -3127,11 +3128,11 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.38"
|
version = "1.0.39"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa"
|
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4003,13 +4004,13 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.94"
|
version = "1.0.96"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a07e33e919ebcd69113d5be0e4d70c5707004ff45188910106854f38b960df4a"
|
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-xid",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4219,6 +4220,7 @@ dependencies = [
|
|||||||
"rand_core",
|
"rand_core",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"testx",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@ -4565,6 +4567,12 @@ version = "0.3.8"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
@ -4580,12 +4588,6 @@ version = "1.9.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-xid"
|
|
||||||
version = "0.2.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode_categories"
|
name = "unicode_categories"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -31,5 +31,7 @@ itertools = { version = "0.10.3" }
|
|||||||
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
|
async-trait = { version = "0.1.56" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
testx = { path = "../../shared/testx" }
|
testx = { path = "../../shared/testx" }
|
||||||
|
@ -248,7 +248,7 @@ mod test {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn full_check() {
|
async fn full_check() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
// account
|
// account
|
||||||
let account = test_create_account(&mut t).await;
|
let account = test_create_account(&mut t).await;
|
||||||
|
@ -268,7 +268,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create_account() {
|
async fn create_account() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let login: String = fake::faker::internet::en::Username().fake();
|
let login: String = fake::faker::internet::en::Username().fake();
|
||||||
let email: String = fake::faker::internet::en::FreeEmail().fake();
|
let email: String = fake::faker::internet::en::FreeEmail().fake();
|
||||||
@ -302,7 +302,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn all_accounts() {
|
async fn all_accounts() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_create_account(&mut t, None, None, None).await;
|
test_create_account(&mut t, None, None, None).await;
|
||||||
test_create_account(&mut t, None, None, None).await;
|
test_create_account(&mut t, None, None, None).await;
|
||||||
@ -316,7 +316,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn update_account_without_pass() {
|
async fn update_account_without_pass() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let original_login: String = fake::faker::internet::en::Username().fake();
|
let original_login: String = fake::faker::internet::en::Username().fake();
|
||||||
let original_email: String = fake::faker::internet::en::FreeEmail().fake();
|
let original_email: String = fake::faker::internet::en::FreeEmail().fake();
|
||||||
@ -364,7 +364,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn update_account_with_pass() {
|
async fn update_account_with_pass() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let original_login: String = fake::faker::internet::en::Username().fake();
|
let original_login: String = fake::faker::internet::en::Username().fake();
|
||||||
let original_email: String = fake::faker::internet::en::FreeEmail().fake();
|
let original_email: String = fake::faker::internet::en::FreeEmail().fake();
|
||||||
@ -413,7 +413,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn find() {
|
async fn find() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let account = test_create_account(&mut t, None, None, None).await;
|
let account = test_create_account(&mut t, None, None, None).await;
|
||||||
|
|
||||||
@ -432,7 +432,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn find_identity_email() {
|
async fn find_identity_email() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let account = test_create_account(&mut t, None, None, None).await;
|
let account = test_create_account(&mut t, None, None, None).await;
|
||||||
|
|
||||||
@ -452,7 +452,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn find_identity_login() {
|
async fn find_identity_login() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let account = test_create_account(&mut t, None, None, None).await;
|
let account = test_create_account(&mut t, None, None, None).await;
|
||||||
|
|
||||||
|
@ -72,6 +72,16 @@ macro_rules! db_async_handler {
|
|||||||
Box::pin(async { $inner_async(msg, pool).await }.into_actor(self))
|
Box::pin(async { $inner_async(msg, pool).await }.into_actor(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl $crate::Queue<$msg> for crate::Transaction {
|
||||||
|
type Result = $res;
|
||||||
|
|
||||||
|
async fn handle(&mut self, msg: $msg) -> Result<Self::Result> {
|
||||||
|
let t = &mut self.t;
|
||||||
|
$async(msg, t).await
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,6 +169,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||||||
|
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
|
config: SharedAppConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SharedDatabase = actix::Addr<Database>;
|
pub type SharedDatabase = actix::Addr<Database>;
|
||||||
@ -167,6 +178,7 @@ impl Clone for Database {
|
|||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pool: self.pool.clone(),
|
pool: self.pool.clone(),
|
||||||
|
config: self.config.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,18 +190,55 @@ impl Database {
|
|||||||
tracing::error!("Failed to connect to database. {e:?}");
|
tracing::error!("Failed to connect to database. {e:?}");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
});
|
});
|
||||||
Database { pool }
|
Self { pool, config }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pool(&self) -> &PgPool {
|
pub fn pool(&self) -> &PgPool {
|
||||||
&self.pool
|
&self.pool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn begin(&self) -> sqlx::Result<Transaction> {
|
||||||
|
Ok(Transaction {
|
||||||
|
t: self.pool.begin().await?,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for Database {
|
impl Actor for Database {
|
||||||
type Context = Context<Self>;
|
type Context = Context<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Transaction {
|
||||||
|
t: sqlx::Transaction<'static, sqlx::Postgres>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transaction {
|
||||||
|
pub async fn commit(self) -> Result<()> {
|
||||||
|
self.t.commit().await.map_err(|e| {
|
||||||
|
tracing::error!("{e:?}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::TransactionFailed
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn rollback(self) -> Result<()> {
|
||||||
|
self.t.rollback().await.map_err(|e| {
|
||||||
|
tracing::error!("{e:?}");
|
||||||
|
dbg!(e);
|
||||||
|
Error::TransactionFailed
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
pub trait Queue<Msg> {
|
||||||
|
type Result;
|
||||||
|
|
||||||
|
async fn handle(&mut self, msg: Msg) -> Result<Self::Result>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Multi-query load for large amount of records to read
|
/// Multi-query load for large amount of records to read
|
||||||
///
|
///
|
||||||
/// Examples
|
/// Examples
|
||||||
@ -203,7 +252,11 @@ impl Actor for Database {
|
|||||||
/// &mut t,
|
/// &mut t,
|
||||||
/// "SELECT id, name FROM products WHERE ",
|
/// "SELECT id, name FROM products WHERE ",
|
||||||
/// " id = "
|
/// " id = "
|
||||||
/// );
|
/// )
|
||||||
|
/// // order by id
|
||||||
|
/// .with_sorting("id ASC")
|
||||||
|
/// // 100 rows per db query
|
||||||
|
/// .with_size(100);
|
||||||
/// let products: Vec<model::Product> = multi.load(4, vec![1, 2, 3, 4].into_iter(), |_| Error::All.into())
|
/// let products: Vec<model::Product> = multi.load(4, vec![1, 2, 3, 4].into_iter(), |_| Error::All.into())
|
||||||
/// .await.unwrap();
|
/// .await.unwrap();
|
||||||
/// t.commit().await.unwrap();
|
/// t.commit().await.unwrap();
|
||||||
@ -214,6 +267,7 @@ pub struct MultiLoad<'transaction, 'transaction2, 'header, 'condition, T> {
|
|||||||
header: &'header str,
|
header: &'header str,
|
||||||
condition: &'condition str,
|
condition: &'condition str,
|
||||||
sort: Option<String>,
|
sort: Option<String>,
|
||||||
|
size: usize,
|
||||||
__phantom: std::marker::PhantomData<T>,
|
__phantom: std::marker::PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,6 +286,7 @@ where
|
|||||||
header,
|
header,
|
||||||
condition,
|
condition,
|
||||||
sort: None,
|
sort: None,
|
||||||
|
size: 20,
|
||||||
__phantom: Default::default(),
|
__phantom: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,6 +296,11 @@ where
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_size(mut self, size: usize) -> Self {
|
||||||
|
self.size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn load<'query, Error, Ids>(
|
pub async fn load<'query, Error, Ids>(
|
||||||
&mut self,
|
&mut self,
|
||||||
len: usize,
|
len: usize,
|
||||||
@ -252,12 +312,14 @@ where
|
|||||||
Error: Fn(sqlx::Error) -> crate::Error,
|
Error: Fn(sqlx::Error) -> crate::Error,
|
||||||
{
|
{
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
|
let size = self.size;
|
||||||
|
|
||||||
for ids in items.fold(
|
for ids in items.fold(
|
||||||
Vec::<Vec<model::RecordId>>::with_capacity(len),
|
Vec::<Vec<model::RecordId>>::with_capacity(len),
|
||||||
|mut v, id| {
|
|mut v, id| {
|
||||||
if matches!(v.last().map(|v| v.len()), Some(20) | None) {
|
let last_len = v.last().map(|v| v.len());
|
||||||
v.push(Vec::with_capacity(20));
|
if last_len == Some(size) || last_len == None {
|
||||||
|
v.push(Vec::with_capacity(size));
|
||||||
}
|
}
|
||||||
v.last_mut().unwrap().push(id);
|
v.last_mut().unwrap().push(id);
|
||||||
v
|
v
|
||||||
|
@ -184,7 +184,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create() {
|
async fn create() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_order_address(&mut t).await;
|
test_order_address(&mut t).await;
|
||||||
|
|
||||||
@ -193,7 +193,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn update() {
|
async fn update() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let original = test_order_address(&mut t).await;
|
let original = test_order_address(&mut t).await;
|
||||||
let updated = super::update_order_address(
|
let updated = super::update_order_address(
|
||||||
@ -231,7 +231,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn order_address() {
|
async fn order_address() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_order_address(&mut t).await;
|
test_order_address(&mut t).await;
|
||||||
|
|
||||||
|
@ -284,7 +284,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create() {
|
async fn create() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_order_item(&mut t, None).await;
|
test_order_item(&mut t, None).await;
|
||||||
|
|
||||||
@ -293,7 +293,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn order_items() {
|
async fn order_items() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let order1 = test_order(&mut t).await;
|
let order1 = test_order(&mut t).await;
|
||||||
test_order_item(&mut t, Some(order1.id)).await;
|
test_order_item(&mut t, Some(order1.id)).await;
|
||||||
|
@ -404,7 +404,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn empty_order_without_cart() {
|
async fn empty_order_without_cart() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let address_id = test_order_address(&mut t).await.id;
|
let address_id = test_order_address(&mut t).await.id;
|
||||||
test_empty_order_without_cart(&mut t, None, address_id).await;
|
test_empty_order_without_cart(&mut t, None, address_id).await;
|
||||||
@ -414,7 +414,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn empty_order_with_cart() {
|
async fn empty_order_with_cart() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let buyer_id = test_account(&mut t).await.id;
|
let buyer_id = test_account(&mut t).await.id;
|
||||||
let address_id = test_order_address(&mut t).await.id;
|
let address_id = test_order_address(&mut t).await.id;
|
||||||
@ -440,7 +440,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn non_empty_order_with_cart() {
|
async fn non_empty_order_with_cart() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let buyer_id = test_account(&mut t).await.id;
|
let buyer_id = test_account(&mut t).await.id;
|
||||||
let address_id = test_order_address(&mut t).await.id;
|
let address_id = test_order_address(&mut t).await.id;
|
||||||
@ -477,7 +477,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn non_empty_order_without_cart() {
|
async fn non_empty_order_without_cart() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let buyer_id = test_account(&mut t).await.id;
|
let buyer_id = test_account(&mut t).await.id;
|
||||||
let address_id = test_order_address(&mut t).await.id;
|
let address_id = test_order_address(&mut t).await.id;
|
||||||
@ -510,7 +510,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn update_by_ext() {
|
async fn update_by_ext() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let address_id = test_order_address(&mut t).await.id;
|
let address_id = test_order_address(&mut t).await.id;
|
||||||
let original = test_empty_order_without_cart(&mut t, None, address_id).await;
|
let original = test_empty_order_without_cart(&mut t, None, address_id).await;
|
||||||
|
@ -192,7 +192,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create_photo() {
|
async fn create_photo() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_photo(&mut t, None, None, None).await;
|
test_photo(&mut t, None, None, None).await;
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn all() {
|
async fn all() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let p1 = test_photo(&mut t, None, None, None).await;
|
let p1 = test_photo(&mut t, None, None, None).await;
|
||||||
let p2 = test_photo(&mut t, None, None, None).await;
|
let p2 = test_photo(&mut t, None, None, None).await;
|
||||||
@ -215,7 +215,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn products_photos() {
|
async fn products_photos() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let product_1 = test_product(&mut t).await;
|
let product_1 = test_product(&mut t).await;
|
||||||
let p1 = test_product_photo(&mut t, product_1.id).await;
|
let p1 = test_product_photo(&mut t, product_1.id).await;
|
||||||
|
@ -165,7 +165,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create_photo() {
|
async fn create_photo() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_product_photo(&mut t).await;
|
test_product_photo(&mut t).await;
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn delete() {
|
async fn delete() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let p1 = test_product_photo(&mut t).await;
|
let p1 = test_product_photo(&mut t).await;
|
||||||
let p2 = test_product_photo(&mut t).await;
|
let p2 = test_product_photo(&mut t).await;
|
||||||
@ -192,7 +192,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create() {
|
async fn create() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_product_photo(&mut t).await;
|
test_product_photo(&mut t).await;
|
||||||
|
|
||||||
|
@ -360,7 +360,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create() {
|
async fn create() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_product(&mut t, None, None, None, None, None, None).await;
|
test_product(&mut t, None, None, None, None, None, None).await;
|
||||||
|
|
||||||
@ -369,7 +369,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn all() {
|
async fn all() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let p1 = test_product(&mut t, None, None, None, None, None, None).await;
|
let p1 = test_product(&mut t, None, None, None, None, None, None).await;
|
||||||
let p2 = test_product(&mut t, None, None, None, None, None, None).await;
|
let p2 = test_product(&mut t, None, None, None, None, None, None).await;
|
||||||
@ -383,7 +383,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn find() {
|
async fn find() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let p1 = test_product(&mut t, None, None, None, None, None, None).await;
|
let p1 = test_product(&mut t, None, None, None, None, None, None).await;
|
||||||
let p2 = test_product(&mut t, None, None, None, None, None, None).await;
|
let p2 = test_product(&mut t, None, None, None, None, None, None).await;
|
||||||
@ -401,7 +401,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn update() {
|
async fn update() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let original = test_product(&mut t, None, None, None, None, None, None).await;
|
let original = test_product(&mut t, None, None, None, None, None, None).await;
|
||||||
let updated = update_product(
|
let updated = update_product(
|
||||||
|
@ -541,7 +541,7 @@ WHERE buyer_id = $1
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create() {
|
async fn create() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_shopping_cart_item(&mut t, None, None).await;
|
test_shopping_cart_item(&mut t, None, None).await;
|
||||||
|
|
||||||
@ -550,7 +550,7 @@ WHERE buyer_id = $1
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn all() {
|
async fn all() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let account_id = test_account(&mut t, None, None, None).await.id;
|
let account_id = test_account(&mut t, None, None, None).await.id;
|
||||||
|
|
||||||
@ -581,7 +581,7 @@ WHERE buyer_id = $1
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn account_cart_with_cart_id() {
|
async fn account_cart_with_cart_id() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let account_id = test_account(&mut t, None, None, None).await.id;
|
let account_id = test_account(&mut t, None, None, None).await.id;
|
||||||
|
|
||||||
@ -618,7 +618,7 @@ WHERE buyer_id = $1
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn account_cart_without_cart_id() {
|
async fn account_cart_without_cart_id() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let account_id = test_account(&mut t, None, None, None).await.id;
|
let account_id = test_account(&mut t, None, None, None).await.id;
|
||||||
|
|
||||||
@ -655,7 +655,7 @@ WHERE buyer_id = $1
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn update() {
|
async fn update() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
let account_id = test_account(&mut t, None, None, None).await.id;
|
let account_id = test_account(&mut t, None, None, None).await.id;
|
||||||
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
let cart1 = test_shopping_cart(&mut t, Some(account_id), ShoppingCartState::Closed).await;
|
||||||
let item = test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
|
let item = test_shopping_cart_item(&mut t, Some(cart1.id), None).await;
|
||||||
|
@ -345,7 +345,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create_shopping_cart() {
|
async fn create_shopping_cart() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let account = test_account(&mut t, None, None, None).await;
|
let account = test_account(&mut t, None, None, None).await;
|
||||||
|
|
||||||
@ -364,7 +364,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn update_shopping_cart() {
|
async fn update_shopping_cart() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let account = test_account(&mut t, None, None, None).await;
|
let account = test_account(&mut t, None, None, None).await;
|
||||||
|
|
||||||
@ -399,7 +399,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn without_cart_ensure_shopping_cart() {
|
async fn without_cart_ensure_shopping_cart() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let account = test_account(&mut t, None, None, None).await;
|
let account = test_account(&mut t, None, None, None).await;
|
||||||
|
|
||||||
@ -429,7 +429,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn with_inactive_cart_ensure_shopping_cart() {
|
async fn with_inactive_cart_ensure_shopping_cart() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let account = test_account(&mut t, None, None, None).await;
|
let account = test_account(&mut t, None, None, None).await;
|
||||||
|
|
||||||
|
@ -267,7 +267,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create_stock() {
|
async fn create_stock() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_stock(&mut t, None, None, None).await;
|
test_stock(&mut t, None, None, None).await;
|
||||||
|
|
||||||
@ -276,7 +276,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn products_stock() {
|
async fn products_stock() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let first = test_stock(&mut t, None, None, None).await;
|
let first = test_stock(&mut t, None, None, None).await;
|
||||||
let second = test_stock(&mut t, None, None, None).await;
|
let second = test_stock(&mut t, None, None, None).await;
|
||||||
@ -296,7 +296,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn all_stocks() {
|
async fn all_stocks() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let first = test_stock(&mut t, None, None, None).await;
|
let first = test_stock(&mut t, None, None, None).await;
|
||||||
let second = test_stock(&mut t, None, None, None).await;
|
let second = test_stock(&mut t, None, None, None).await;
|
||||||
@ -309,7 +309,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn delete_stock() {
|
async fn delete_stock() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let first = test_stock(&mut t, None, None, None).await;
|
let first = test_stock(&mut t, None, None, None).await;
|
||||||
let second = test_stock(&mut t, None, None, None).await;
|
let second = test_stock(&mut t, None, None, None).await;
|
||||||
@ -332,7 +332,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn update_stock() {
|
async fn update_stock() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let first = test_stock(&mut t, None, None, None).await;
|
let first = test_stock(&mut t, None, None, None).await;
|
||||||
let second = test_stock(&mut t, None, None, None).await;
|
let second = test_stock(&mut t, None, None, None).await;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use actix::Message;
|
use actix::Message;
|
||||||
use model::{AccountId, Audience, Token};
|
use model::{AccountId, Audience, Token, TokenId};
|
||||||
|
|
||||||
use crate::{db_async_handler, Result};
|
use crate::{db_async_handler, Result};
|
||||||
|
|
||||||
@ -138,6 +138,31 @@ RETURNING id, customer_id, role, issuer, subject, audience, expiration_time, not
|
|||||||
crate::Error::Token(Error::Create)
|
crate::Error::Token(Error::Create)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "Result<Option<Token>>")]
|
||||||
|
pub struct DeleteToken {
|
||||||
|
pub token_id: TokenId,
|
||||||
|
}
|
||||||
|
|
||||||
|
db_async_handler!(DeleteToken, delete_token, Option<Token>, inner_delete_token);
|
||||||
|
|
||||||
|
pub(crate) async fn delete_token(
|
||||||
|
msg: DeleteToken,
|
||||||
|
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
|
) -> Result<Option<Token>> {
|
||||||
|
sqlx::query_as(r#"
|
||||||
|
DELETE FROM tokens
|
||||||
|
WHERE id = $1
|
||||||
|
RETURNING id, customer_id, role, issuer, subject, audience, expiration_time, not_before_time, issued_at_time, jwt_id
|
||||||
|
"#)
|
||||||
|
.bind(msg.token_id)
|
||||||
|
.fetch_optional(t)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("{e:?}");
|
||||||
|
crate::Error::Token(Error::Jti)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@ -205,7 +230,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create_token() {
|
async fn create_token() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
super::create_token(
|
super::create_token(
|
||||||
CreateToken {
|
CreateToken {
|
||||||
@ -222,7 +247,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn create_extended_token() {
|
async fn create_extended_token() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
test_create_account(&mut t).await;
|
test_create_account(&mut t).await;
|
||||||
|
|
||||||
@ -231,7 +256,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn find_by_jti() {
|
async fn find_by_jti() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let original = test_create_token_extended(&mut t, None, None, None, None, None).await;
|
let original = test_create_token_extended(&mut t, None, None, None, None, None).await;
|
||||||
|
|
||||||
@ -250,7 +275,7 @@ mod tests {
|
|||||||
|
|
||||||
#[actix::test]
|
#[actix::test]
|
||||||
async fn find_by_jti_expired() {
|
async fn find_by_jti_expired() {
|
||||||
testx::db_t!(t);
|
testx::db_t_ref!(t);
|
||||||
|
|
||||||
let original = test_create_token_extended(
|
let original = test_create_token_extended(
|
||||||
&mut t,
|
&mut t,
|
||||||
|
@ -217,7 +217,7 @@ pub(crate) async fn request_payment(
|
|||||||
},
|
},
|
||||||
Error::UnavailableShoppingCart
|
Error::UnavailableShoppingCart
|
||||||
);
|
);
|
||||||
let mut items =
|
let items =
|
||||||
cart_items
|
cart_items
|
||||||
.iter()
|
.iter()
|
||||||
.fold(HashMap::with_capacity(cart_items.len()), |mut agg, item| {
|
.fold(HashMap::with_capacity(cart_items.len()), |mut agg, item| {
|
||||||
@ -233,60 +233,34 @@ pub(crate) async fn request_payment(
|
|||||||
Error::UnavailableShoppingCart
|
Error::UnavailableShoppingCart
|
||||||
);
|
);
|
||||||
|
|
||||||
// let payment_required = {
|
let redirect_uri = match msg.payment_method {
|
||||||
// let l = config.lock();
|
PaymentMethod::PayU => {
|
||||||
// l.payment().optional_payment() != false
|
let (redirect_uri, ext_order_id) = pay_u_adapter::CreatePayment {
|
||||||
// };
|
client,
|
||||||
let redirect_uri = {
|
buyer: msg.buyer,
|
||||||
let pay_u::res::CreateOrder {
|
customer_ip: msg.customer_ip,
|
||||||
status: _,
|
currency: msg.currency,
|
||||||
redirect_uri,
|
description: format!("Order #{}", db_order.id),
|
||||||
order_id,
|
cart_products,
|
||||||
ext_order_id: _,
|
items,
|
||||||
} = {
|
order_ext_id: db_order.order_ext_id.to_string(),
|
||||||
client
|
notify_uri,
|
||||||
.lock()
|
continue_uri,
|
||||||
.create_order(
|
}
|
||||||
pay_u::req::OrderCreate::build(
|
.create_payment()
|
||||||
msg.buyer.into(),
|
.await?;
|
||||||
msg.customer_ip,
|
|
||||||
msg.currency,
|
|
||||||
format!("Order #{}", db_order.id),
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
tracing::error!("{}", e);
|
|
||||||
Error::InvalidOrder
|
|
||||||
})?
|
|
||||||
.with_products(cart_products.into_iter().map(|p| {
|
|
||||||
pay_u::Product::new(
|
|
||||||
p.name.to_string(),
|
|
||||||
**p.price,
|
|
||||||
items
|
|
||||||
.remove(&p.id)
|
|
||||||
.map(|(quantity, _)| **quantity as u32)
|
|
||||||
.unwrap_or_default(),
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.with_ext_order_id(db_order.order_ext_id.to_string())
|
|
||||||
.with_notify_url(notify_uri)
|
|
||||||
.with_continue_url(continue_uri),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
tracing::error!("{}", e);
|
|
||||||
Error::PaymentFailed
|
|
||||||
})?
|
|
||||||
};
|
|
||||||
|
|
||||||
query_db!(
|
query_db!(
|
||||||
db,
|
db,
|
||||||
database_manager::SetOrderServiceId {
|
database_manager::SetOrderServiceId {
|
||||||
service_order_id: order_id.0,
|
service_order_id: ext_order_id.into_inner(),
|
||||||
id: db_order.id,
|
id: db_order.id,
|
||||||
},
|
},
|
||||||
Error::CreateOrder
|
Error::CreateOrder
|
||||||
);
|
);
|
||||||
redirect_uri
|
redirect_uri
|
||||||
|
}
|
||||||
|
PaymentMethod::PaymentOnTheSpot => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let order_items = query_db!(
|
let order_items = query_db!(
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use model::*;
|
||||||
|
|
||||||
|
use crate::{Buyer, Error, PayUClient, Result};
|
||||||
|
|
||||||
|
pub struct CreatePayment {
|
||||||
|
pub client: PayUClient,
|
||||||
|
pub buyer: Buyer,
|
||||||
|
pub customer_ip: String,
|
||||||
|
pub currency: String,
|
||||||
|
pub description: String,
|
||||||
|
pub cart_products: Vec<model::Product>,
|
||||||
|
pub items: HashMap<ProductId, (model::Quantity, QuantityUnit)>,
|
||||||
|
pub order_ext_id: String,
|
||||||
|
pub notify_uri: String,
|
||||||
|
pub continue_uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreatePayment {
|
||||||
|
pub(crate) async fn create_payment(self) -> Result<(String, ExtOrderId)> {
|
||||||
|
let CreatePayment {
|
||||||
|
client,
|
||||||
|
buyer,
|
||||||
|
customer_ip,
|
||||||
|
currency,
|
||||||
|
description,
|
||||||
|
cart_products,
|
||||||
|
mut items,
|
||||||
|
order_ext_id,
|
||||||
|
notify_uri,
|
||||||
|
continue_uri,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let pay_u::res::CreateOrder {
|
||||||
|
status: _,
|
||||||
|
redirect_uri,
|
||||||
|
order_id,
|
||||||
|
ext_order_id: _,
|
||||||
|
} = client
|
||||||
|
.lock()
|
||||||
|
.create_order(
|
||||||
|
pay_u::req::OrderCreate::build(buyer.into(), customer_ip, currency, description)
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("{}", e);
|
||||||
|
Error::InvalidOrder
|
||||||
|
})?
|
||||||
|
.with_products(cart_products.into_iter().map(|p| {
|
||||||
|
pay_u::Product::new(
|
||||||
|
p.name.to_string(),
|
||||||
|
**p.price,
|
||||||
|
items
|
||||||
|
.remove(&p.id)
|
||||||
|
.map(|(quantity, _)| **quantity as u32)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.with_ext_order_id(order_ext_id)
|
||||||
|
.with_notify_url(notify_uri)
|
||||||
|
.with_continue_url(continue_uri),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("{}", e);
|
||||||
|
Error::PaymentFailed
|
||||||
|
})?;
|
||||||
|
Ok((redirect_uri, ExtOrderId::new(order_id.0)))
|
||||||
|
}
|
||||||
|
}
|
@ -35,3 +35,6 @@ sha2 = { version = "0.10", features = [] }
|
|||||||
tokio = { version = "1.17", features = ["full"] }
|
tokio = { version = "1.17", features = ["full"] }
|
||||||
futures = { version = "0.3", features = [] }
|
futures = { version = "0.3", features = [] }
|
||||||
futures-util = { version = "0.3", features = [] }
|
futures-util = { version = "0.3", features = [] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
testx = { path = "../../shared/testx" }
|
||||||
|
@ -1,3 +1,75 @@
|
|||||||
|
//! Tokens management system.
|
||||||
|
//! It's responsible for creating and validating all tokens.
|
||||||
|
//!
|
||||||
|
//! Application flow goes like this:
|
||||||
|
//!
|
||||||
|
//! ```ascii
|
||||||
|
//! Client API TokenManager Database
|
||||||
|
//!
|
||||||
|
//! │ │ │ │
|
||||||
|
//! │ │ │ │
|
||||||
|
//! │ │ │ ┌───────────────►│
|
||||||
|
//! ├────────────────►├──────────────────►├──────►│ │
|
||||||
|
//! │ Sign In │ CreatePair │ └───────────────►│
|
||||||
|
//! │ │ │ Create │
|
||||||
|
//! │ │ │ * AccessToken │
|
||||||
|
//! │ │ │ * RefreshToken │
|
||||||
|
//! │ │ │ │
|
||||||
|
//! │ │ │ │
|
||||||
|
//!
|
||||||
|
//! │ │ │ │
|
||||||
|
//! ├────────────────►├──────────────────►├───────────────────────►│
|
||||||
|
//! │ Validate token │ ValidateToken │ Load token │
|
||||||
|
//! │ │ (string) │◄───────────────────────┤
|
||||||
|
//! │ │ │ │
|
||||||
|
//! │ │ Is Valid? │
|
||||||
|
//! │ │ │ │
|
||||||
|
//! │ │◄──────────────── YES │
|
||||||
|
//! │ │ AccessToken │ │
|
||||||
|
//! │ │ │ │
|
||||||
|
//! │ │ │ │
|
||||||
|
//! │ │◄──────────────── NO │
|
||||||
|
//! │ │ Error │ │
|
||||||
|
//!
|
||||||
|
//! │ │ │ │
|
||||||
|
//! │ │ │ │
|
||||||
|
//! │ │ │ ┌───────────────►│
|
||||||
|
//! ├────────────────►├──────────────────►├──────►│ │
|
||||||
|
//! │ Refresh token │ CreatePair │ └───────────────►│
|
||||||
|
//! │ │ │ Create │
|
||||||
|
//! │ │◄──────────────────┤ * AccessToken │
|
||||||
|
//! │ │ Access Token │ * RefreshToken │
|
||||||
|
//! │ │ Refresh Token │ │
|
||||||
|
//! │ │ │ │
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! If you need to operate on tokens from API or any other actor you should
|
||||||
|
//! always use this actor and never touch database directly.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use actix::{Actor, Addr};
|
||||||
|
//! use config::SharedAppConfig;
|
||||||
|
//! use database_manager::Database;
|
||||||
|
//! use token_manager::*;
|
||||||
|
//! use model::*;
|
||||||
|
//!
|
||||||
|
//! async fn tokens(db: Addr<Database>, config: SharedAppConfig) {
|
||||||
|
//! let manager = TokenManager::new(config, db);
|
||||||
|
//!
|
||||||
|
//! let manager_addr = manager.start();
|
||||||
|
//!
|
||||||
|
//! let AuthPair { access_token, access_token_string, refresh_token_string, .. } = manager_addr.send(CreatePair {
|
||||||
|
//! customer_id: uuid::Uuid::new_v4(),
|
||||||
|
//! account_id: AccountId::from(0),
|
||||||
|
//! role: Role::Admin
|
||||||
|
//! }).await.unwrap().unwrap();
|
||||||
|
//!
|
||||||
|
//! manager_addr.send(Validate { token: access_token_string }).await.unwrap().unwrap();
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
@ -138,6 +210,22 @@ impl TokenManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates single token, it's mostly used by [CreatePair]
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use actix::Addr;
|
||||||
|
/// use model::{AccountId, Role};
|
||||||
|
/// use token_manager::*;
|
||||||
|
/// async fn create_pair(token_manager: Addr<token_manager::TokenManager>) {
|
||||||
|
/// match token_manager.send(CreateToken { customer_id: uuid::Uuid::new_v4(), role: Role::Admin, subject: AccountId::from(1), audience: None, exp: None }).await {
|
||||||
|
/// Ok(Ok(pair)) => {}
|
||||||
|
/// Ok(Err(manager_error)) => {}
|
||||||
|
/// Err(actor_error) => {}
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "Result<(Token, AccessTokenString)>")]
|
#[rtype(result = "Result<(Token, AccessTokenString)>")]
|
||||||
pub struct CreateToken {
|
pub struct CreateToken {
|
||||||
@ -256,12 +344,28 @@ pub struct AuthPair {
|
|||||||
pub refresh_token_string: model::RefreshTokenString,
|
pub refresh_token_string: model::RefreshTokenString,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates access token and refresh token
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use actix::Addr;
|
||||||
|
/// use model::{AccountId, Role};
|
||||||
|
/// use token_manager::CreatePair;
|
||||||
|
/// async fn create_pair(token_manager: Addr<token_manager::TokenManager>) {
|
||||||
|
/// match token_manager.send(CreatePair { customer_id: uuid::Uuid::new_v4(), account_id: AccountId::from(0), role: Role::Admin }).await {
|
||||||
|
/// Ok(Ok(pair)) => {}
|
||||||
|
/// Ok(Err(manager_error)) => {}
|
||||||
|
/// Err(actor_error) => {}
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "Result<AuthPair>")]
|
#[rtype(result = "Result<AuthPair>")]
|
||||||
pub struct CreatePair {
|
pub struct CreatePair {
|
||||||
pub customer_id: uuid::Uuid,
|
pub customer_id: uuid::Uuid,
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
pub id: AccountId,
|
pub account_id: AccountId,
|
||||||
}
|
}
|
||||||
|
|
||||||
token_async_handler!(CreatePair, create_pair, AuthPair);
|
token_async_handler!(CreatePair, create_pair, AuthPair);
|
||||||
@ -276,7 +380,7 @@ pub(crate) async fn create_pair(
|
|||||||
CreateToken {
|
CreateToken {
|
||||||
customer_id: msg.customer_id,
|
customer_id: msg.customer_id,
|
||||||
role: msg.role,
|
role: msg.role,
|
||||||
subject: msg.id,
|
subject: msg.account_id,
|
||||||
audience: Some(model::Audience::Web),
|
audience: Some(model::Audience::Web),
|
||||||
exp: None
|
exp: None
|
||||||
},
|
},
|
||||||
@ -287,7 +391,7 @@ pub(crate) async fn create_pair(
|
|||||||
CreateToken {
|
CreateToken {
|
||||||
customer_id: msg.customer_id,
|
customer_id: msg.customer_id,
|
||||||
role: msg.role,
|
role: msg.role,
|
||||||
subject: msg.id,
|
subject: msg.account_id,
|
||||||
audience: Some(model::Audience::Web),
|
audience: Some(model::Audience::Web),
|
||||||
exp: Some((chrono::Utc::now() + chrono::Duration::days(31)).naive_utc())
|
exp: Some((chrono::Utc::now() + chrono::Duration::days(31)).naive_utc())
|
||||||
},
|
},
|
||||||
@ -305,6 +409,22 @@ pub(crate) async fn create_pair(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if token is still valid
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use actix::Addr;
|
||||||
|
/// use model::{AccessTokenString, AccountId, Role};
|
||||||
|
/// use token_manager::{CreatePair, Validate};
|
||||||
|
/// async fn create_pair(token_manager: Addr<token_manager::TokenManager>, token: AccessTokenString) {
|
||||||
|
/// match token_manager.send(Validate { token }).await {
|
||||||
|
/// Ok(Ok(pair)) => {}
|
||||||
|
/// Ok(Err(manager_error)) => {}
|
||||||
|
/// Err(actor_error) => {}
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(result = "Result<Token>")]
|
#[rtype(result = "Result<Token>")]
|
||||||
pub struct Validate {
|
pub struct Validate {
|
||||||
@ -349,30 +469,14 @@ pub(crate) async fn validate(
|
|||||||
return Err(Error::Validate);
|
return Err(Error::Validate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !validate_pair(&claims, "cti", token.customer_id, validate_uuid) {
|
validate_pair(&claims, "cti", token.customer_id, validate_uuid)?;
|
||||||
return Err(Error::Invalid);
|
validate_pair(&claims, "arl", token.role, eq)?;
|
||||||
}
|
validate_pair(&claims, "iss", &token.issuer, eq)?;
|
||||||
if !validate_pair(&claims, "arl", token.role, |left, right| right == left) {
|
validate_pair(&claims, "sub", token.subject, validate_num)?;
|
||||||
return Err(Error::Invalid);
|
validate_pair(&claims, "aud", token.audience, eq)?;
|
||||||
}
|
validate_pair(&claims, "exp", &token.expiration_time, validate_time)?;
|
||||||
if !validate_pair(&claims, "iss", &token.issuer, |left, right| right == left) {
|
validate_pair(&claims, "nbt", &token.not_before_time, validate_time)?;
|
||||||
return Err(Error::Invalid);
|
validate_pair(&claims, "iat", &token.issued_at_time, validate_time)?;
|
||||||
}
|
|
||||||
if !validate_pair(&claims, "sub", token.subject, validate_num) {
|
|
||||||
return Err(Error::Invalid);
|
|
||||||
}
|
|
||||||
if !validate_pair(&claims, "aud", token.audience, |left, right| right == left) {
|
|
||||||
return Err(Error::Invalid);
|
|
||||||
}
|
|
||||||
if !validate_pair(&claims, "exp", &token.expiration_time, validate_time) {
|
|
||||||
return Err(Error::Invalid);
|
|
||||||
}
|
|
||||||
if !validate_pair(&claims, "nbt", &token.not_before_time, validate_time) {
|
|
||||||
return Err(Error::Invalid);
|
|
||||||
}
|
|
||||||
if !validate_pair(&claims, "iat", &token.issued_at_time, validate_time) {
|
|
||||||
return Err(Error::Invalid);
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::info!("JWT token valid");
|
tracing::info!("JWT token valid");
|
||||||
Ok(token)
|
Ok(token)
|
||||||
@ -383,31 +487,155 @@ fn build_key(secret: String) -> Result<Hmac<Sha256>> {
|
|||||||
Ok(key) => Ok(key),
|
Ok(key) => Ok(key),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("{e:?}");
|
tracing::error!("{e:?}");
|
||||||
|
dbg!(e);
|
||||||
Err(Error::ValidateInternal)
|
Err(Error::ValidateInternal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_pair<F, V>(claims: &BTreeMap<String, String>, key: &str, v: V, cmp: F) -> bool
|
#[inline(always)]
|
||||||
|
fn validate_pair<F, V>(
|
||||||
|
claims: &BTreeMap<String, String>,
|
||||||
|
key: &str,
|
||||||
|
v: V,
|
||||||
|
cmp: F,
|
||||||
|
) -> std::result::Result<(), Error>
|
||||||
where
|
where
|
||||||
F: FnOnce(&str, V) -> bool,
|
F: for<'s> FnOnce(V, &'s str) -> bool,
|
||||||
V: PartialEq,
|
V: PartialEq,
|
||||||
{
|
{
|
||||||
claims.get(key).map(|s| cmp(s, v)).unwrap_or_default()
|
claims
|
||||||
|
.get(key)
|
||||||
|
.map(|s| cmp(v, s.as_str()))
|
||||||
|
.unwrap_or_default()
|
||||||
|
.then_some(())
|
||||||
|
.ok_or(Error::Invalid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_time(left: &str, right: &NaiveDateTime) -> bool {
|
#[inline(always)]
|
||||||
chrono::DateTime::parse_from_str(left, "%+")
|
fn eq<V>(value: V, text: &str) -> bool
|
||||||
.map(|t| t.naive_utc() == *right)
|
where
|
||||||
|
V: for<'s> PartialEq<&'s str>,
|
||||||
|
{
|
||||||
|
value == text
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn validate_time(left: &NaiveDateTime, right: &str) -> bool {
|
||||||
|
chrono::DateTime::parse_from_str(right, "%+")
|
||||||
|
.map(|t| t.naive_utc() == *left)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_num(left: &str, right: i32) -> bool {
|
#[inline(always)]
|
||||||
left.parse::<i32>().map(|n| n == right).unwrap_or_default()
|
fn validate_num(left: i32, right: &str) -> bool {
|
||||||
|
right.parse::<i32>().map(|n| left == n).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_uuid(left: &str, right: uuid::Uuid) -> bool {
|
#[inline(always)]
|
||||||
uuid::Uuid::from_str(left)
|
fn validate_uuid(left: uuid::Uuid, right: &str) -> bool {
|
||||||
.map(|u| u == right)
|
uuid::Uuid::from_str(right)
|
||||||
|
.map(|u| u == left)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use actix::Actor;
|
||||||
|
use config::UpdateConfig;
|
||||||
|
use database_manager::Database;
|
||||||
|
use model::*;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct NoOpts;
|
||||||
|
|
||||||
|
impl UpdateConfig for NoOpts {}
|
||||||
|
|
||||||
|
#[actix::test]
|
||||||
|
async fn create_token() {
|
||||||
|
testx::db!(config, db);
|
||||||
|
let db = db.start();
|
||||||
|
|
||||||
|
let (token, _text) = super::create_token(
|
||||||
|
CreateToken {
|
||||||
|
customer_id: Default::default(),
|
||||||
|
role: Role::Admin,
|
||||||
|
subject: AccountId::from(1),
|
||||||
|
audience: None,
|
||||||
|
exp: None,
|
||||||
|
},
|
||||||
|
db.clone(),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.send(database_manager::DeleteToken { token_id: token.id })
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix::test]
|
||||||
|
async fn create_pair() {
|
||||||
|
testx::db!(config, db);
|
||||||
|
let db = db.start();
|
||||||
|
|
||||||
|
let AuthPair {
|
||||||
|
access_token,
|
||||||
|
access_token_string: _,
|
||||||
|
refresh_token_string: _,
|
||||||
|
_refresh_token,
|
||||||
|
} = super::create_pair(
|
||||||
|
CreatePair {
|
||||||
|
customer_id: Default::default(),
|
||||||
|
role: Role::Admin,
|
||||||
|
account_id: AccountId::from(0),
|
||||||
|
},
|
||||||
|
db.clone(),
|
||||||
|
config,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.send(database_manager::DeleteToken {
|
||||||
|
token_id: access_token.id,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
db.send(database_manager::DeleteToken {
|
||||||
|
token_id: _refresh_token.id,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix::test]
|
||||||
|
async fn validate() {
|
||||||
|
testx::db!(config, db);
|
||||||
|
let db = db.start();
|
||||||
|
|
||||||
|
let (token, text) = super::create_token(
|
||||||
|
CreateToken {
|
||||||
|
customer_id: Default::default(),
|
||||||
|
role: Role::Admin,
|
||||||
|
subject: AccountId::from(1),
|
||||||
|
audience: None,
|
||||||
|
exp: None,
|
||||||
|
},
|
||||||
|
db.clone(),
|
||||||
|
config.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
super::validate(Validate { token: text }, db.clone(), config.clone())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
db.send(database_manager::DeleteToken { token_id: token.id })
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -55,7 +55,7 @@ async fn sign_in(
|
|||||||
.send(token_manager::CreatePair {
|
.send(token_manager::CreatePair {
|
||||||
customer_id: account.customer_id,
|
customer_id: account.customer_id,
|
||||||
role: account.role,
|
role: account.role,
|
||||||
id: account.id,
|
account_id: account.id,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| routes::Error::CriticalFailure)??;
|
.map_err(|_| routes::Error::CriticalFailure)??;
|
||||||
|
@ -52,7 +52,7 @@ async fn refresh_token(
|
|||||||
.send(token_manager::CreatePair {
|
.send(token_manager::CreatePair {
|
||||||
customer_id: account.customer_id,
|
customer_id: account.customer_id,
|
||||||
role: account.role,
|
role: account.role,
|
||||||
id: account.id,
|
account_id: account.id,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| routes::Error::CriticalFailure)??;
|
.map_err(|_| routes::Error::CriticalFailure)??;
|
||||||
|
@ -163,7 +163,7 @@ pub async fn create_account(
|
|||||||
.send(token_manager::CreatePair {
|
.send(token_manager::CreatePair {
|
||||||
customer_id: account.customer_id,
|
customer_id: account.customer_id,
|
||||||
role: account.role,
|
role: account.role,
|
||||||
id: account.id,
|
account_id: account.id,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| routes::Error::CriticalFailure)??;
|
.map_err(|_| routes::Error::CriticalFailure)??;
|
||||||
@ -205,7 +205,7 @@ async fn sign_in(
|
|||||||
.send(token_manager::CreatePair {
|
.send(token_manager::CreatePair {
|
||||||
customer_id: account.customer_id,
|
customer_id: account.customer_id,
|
||||||
role: account.role,
|
role: account.role,
|
||||||
id: account.id,
|
account_id: account.id,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|_| routes::Error::CriticalFailure)??;
|
.map_err(|_| routes::Error::CriticalFailure)??;
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
psql postgres postgres -c "DROP DATABASE bazzar_test"
|
psql postgres postgres -c "DROP DATABASE bazzar_test"
|
||||||
psql postgres postgres -c "CREATE DATABASE bazzar_test"
|
psql postgres postgres -c "CREATE DATABASE bazzar_test"
|
||||||
sqlx migrate run --database-url='postgres://postgres@localhost/bazzar_test'
|
sqlx migrate run --database-url='postgres://postgres@localhost/bazzar_test'
|
||||||
cargo test
|
cargo test --all
|
||||||
|
@ -901,24 +901,34 @@ pub struct Stock {
|
|||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Display, Deref)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Display, Deref)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct OrderAddressId(RecordId);
|
pub struct OrderAddressId(RecordId);
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Display, Deref)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Display, Deref)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct OrderId(RecordId);
|
pub struct OrderId(RecordId);
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
#[cfg_attr(feature = "db", derive(sqlx::Type))]
|
||||||
#[cfg_attr(feature = "db", sqlx(transparent))]
|
#[cfg_attr(feature = "db", sqlx(transparent))]
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Display, Deref)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Display, Deref)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ExtOrderId(String);
|
pub struct ExtOrderId(String);
|
||||||
|
|
||||||
|
impl ExtOrderId {
|
||||||
|
pub fn new<S: Into<String>>(s: S) -> Self {
|
||||||
|
Self(s.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> String {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! db_t {
|
macro_rules! db_t_ref {
|
||||||
($t: ident) => {
|
($t: ident) => {
|
||||||
let config = config::default_load(&mut NoOpts);
|
let config = config::default_load(&mut NoOpts);
|
||||||
config
|
config
|
||||||
@ -13,6 +13,19 @@ macro_rules! db_t {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! db {
|
||||||
|
($config: ident, $db: ident) => {
|
||||||
|
let $config = config::default_load(&mut NoOpts);
|
||||||
|
$config
|
||||||
|
.lock()
|
||||||
|
.database_mut()
|
||||||
|
.set_url("postgres://postgres@localhost/bazzar_test");
|
||||||
|
|
||||||
|
let $db = Database::build($config.clone()).await;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! db_rollback {
|
macro_rules! db_rollback {
|
||||||
($t: expr) => {
|
($t: expr) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user