bazzar/actors/database_manager/src/accounts.rs

473 lines
12 KiB
Rust
Raw Normal View History

2022-05-06 16:02:38 +02:00
#[cfg(feature = "dummy")]
use fake::Fake;
use model::*;
2022-05-08 09:47:05 +02:00
use crate::{db_async_handler, Result};
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, thiserror::Error)]
2022-05-22 14:19:11 +02:00
#[serde(rename_all = "kebab-case")]
pub enum Error {
#[error("Can't create account")]
CantCreate,
#[error("Can't find account does to lack of identity")]
NoIdentity,
#[error("Account does not exists")]
NotExists,
#[error("Failed to load all accounts")]
All,
2022-06-04 16:05:18 +02:00
#[error("Can't update account")]
CantUpdate,
}
#[derive(actix::Message)]
#[rtype(result = "Result<Vec<FullAccount>>")]
pub struct AllAccounts;
db_async_handler!(
AllAccounts,
all_accounts,
Vec<FullAccount>,
inner_all_accounts
);
pub(crate) async fn all_accounts(
_msg: AllAccounts,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<Vec<FullAccount>> {
sqlx::query_as(
r#"
2022-04-19 16:49:30 +02:00
SELECT id, email, login, pass_hash, role, customer_id, state
FROM accounts
"#,
)
.fetch_all(pool)
.await
.map_err(|e| {
tracing::error!("{e:?}");
super::Error::Account(Error::All)
})
}
2022-05-06 16:02:38 +02:00
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
2022-05-08 09:47:05 +02:00
#[derive(actix::Message, Debug)]
#[rtype(result = "Result<FullAccount>")]
pub struct CreateAccount {
pub email: Email,
pub login: Login,
pub pass_hash: PassHash,
pub role: Role,
}
db_async_handler!(
CreateAccount,
create_account,
FullAccount,
inner_create_account
);
2022-11-01 22:09:58 +01:00
pub async fn create_account(
msg: CreateAccount,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<FullAccount> {
sqlx::query_as(
r#"
INSERT INTO accounts (login, email, role, pass_hash)
VALUES ($1, $2, $3, $4)
2022-04-19 16:49:30 +02:00
RETURNING id, email, login, pass_hash, role, customer_id, state
2022-04-18 08:22:51 +02:00
"#,
)
.bind(msg.login)
.bind(msg.email)
.bind(msg.role)
.bind(msg.pass_hash)
.fetch_one(pool)
2022-04-18 08:22:51 +02:00
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-04-18 08:22:51 +02:00
super::Error::Account(Error::CantCreate)
})
}
2022-05-06 16:02:38 +02:00
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
2022-04-18 08:22:51 +02:00
#[derive(actix::Message)]
#[rtype(result = "Result<FullAccount>")]
pub struct UpdateAccount {
pub id: AccountId,
pub email: Email,
pub login: Login,
2022-04-19 16:49:30 +02:00
pub pass_hash: Option<PassHash>,
2022-04-18 08:22:51 +02:00
pub role: Role,
2022-04-19 16:49:30 +02:00
pub state: AccountState,
2022-04-18 08:22:51 +02:00
}
db_async_handler!(
UpdateAccount,
update_account,
FullAccount,
inner_update_account
);
pub(crate) async fn update_account(
msg: UpdateAccount,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<FullAccount> {
2022-04-19 16:49:30 +02:00
match msg.pass_hash {
Some(hash) => sqlx::query_as(
r#"
UPDATE accounts
2022-06-04 16:05:18 +02:00
SET login = $2, email = $3, role = $4, pass_hash = $5, state = $6
2022-04-19 16:49:30 +02:00
WHERE id = $1
RETURNING id, email, login, pass_hash, role, customer_id, state
"#,
)
2022-06-04 16:05:18 +02:00
.bind(msg.id)
2022-04-19 16:49:30 +02:00
.bind(msg.login)
.bind(msg.email)
.bind(msg.role)
.bind(hash)
.bind(msg.state),
None => sqlx::query_as(
r#"
2022-04-18 08:22:51 +02:00
UPDATE accounts
2022-06-04 16:05:18 +02:00
SET login = $2, email = $3, role = $4, state = $5
2022-04-18 08:22:51 +02:00
WHERE id = $1
2022-04-19 16:49:30 +02:00
RETURNING id, email, login, pass_hash, role, customer_id, state
"#,
2022-04-19 16:49:30 +02:00
)
2022-06-04 16:05:18 +02:00
.bind(msg.id)
2022-04-19 16:49:30 +02:00
.bind(msg.login)
.bind(msg.email)
.bind(msg.role)
.bind(msg.state),
}
.fetch_one(pool)
.await
.map_err(|e| {
tracing::error!("{e:?}");
2022-06-04 16:05:18 +02:00
super::Error::Account(Error::CantUpdate)
})
}
#[derive(actix::Message)]
#[rtype(result = "Result<FullAccount>")]
pub struct FindAccount {
pub account_id: AccountId,
}
db_async_handler!(FindAccount, find_account, FullAccount, inner_find_account);
pub(crate) async fn find_account(
msg: FindAccount,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<FullAccount> {
sqlx::query_as(
r#"
2022-04-19 16:49:30 +02:00
SELECT id, email, login, pass_hash, role, customer_id, state
FROM accounts
WHERE id = $1
"#,
)
.bind(msg.account_id)
.fetch_one(pool)
.await
.map_err(|e| {
tracing::error!("{e:?}");
super::Error::Account(Error::NotExists)
})
}
#[derive(actix::Message)]
#[rtype(result = "Result<FullAccount>")]
pub struct AccountByIdentity {
pub login: Option<Login>,
pub email: Option<Email>,
}
db_async_handler!(
AccountByIdentity,
account_by_identity,
FullAccount,
inner_account_by_identity
);
pub(crate) async fn account_by_identity(
msg: AccountByIdentity,
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<FullAccount> {
match (msg.login, msg.email) {
(Some(login), None) => sqlx::query_as(
r#"
2022-04-19 16:49:30 +02:00
SELECT id, email, login, pass_hash, role, customer_id, state
FROM accounts
WHERE login = $1
"#,
)
.bind(login),
(None, Some(email)) => sqlx::query_as(
r#"
2022-04-19 16:49:30 +02:00
SELECT id, email, login, pass_hash, role, customer_id, state
FROM accounts
WHERE email = $1
"#,
)
.bind(email),
(Some(login), Some(email)) => sqlx::query_as(
r#"
2022-04-19 16:49:30 +02:00
SELECT id, email, login, pass_hash, role, customer_id, state
FROM accounts
WHERE login = $1 AND email = $2
"#,
)
.bind(login)
.bind(email),
_ => return Err(super::Error::Account(Error::NoIdentity)),
}
.fetch_one(pool)
.await
.map_err(|e| {
tracing::error!("{e:?}");
super::Error::Account(Error::CantCreate)
})
}
2022-06-04 16:05:18 +02:00
#[cfg(test)]
mod tests {
use config::UpdateConfig;
use fake::Fake;
use model::*;
use crate::*;
pub struct NoOpts;
impl UpdateConfig for NoOpts {}
async fn test_create_account(
t: &mut sqlx::Transaction<'_, sqlx::Postgres>,
2022-06-04 16:05:18 +02:00
login: Option<String>,
email: Option<String>,
hash: Option<String>,
) -> FullAccount {
use fake::faker::internet::en;
let login: String = login.unwrap_or_else(|| en::Username().fake());
let email: String = email.unwrap_or_else(|| en::FreeEmail().fake());
let hash: String = hash.unwrap_or_else(|| en::Password(10..20).fake());
super::create_account(
CreateAccount {
email: Email::new(email),
login: Login::new(login),
pass_hash: PassHash::new(hash),
role: Role::Admin,
},
t,
)
2022-06-04 16:05:18 +02:00
.await
.unwrap()
}
#[actix::test]
async fn create_account() {
testx::db_t_ref!(t);
2022-06-04 16:05:18 +02:00
let login: String = fake::faker::internet::en::Username().fake();
let email: String = fake::faker::internet::en::FreeEmail().fake();
let hash: String = fake::faker::internet::en::Password(10..20).fake();
let account: FullAccount = super::create_account(
CreateAccount {
2022-06-04 16:05:18 +02:00
email: Email::new(&email),
login: Login::new(&login),
pass_hash: PassHash::new(&hash),
role: Role::Admin,
},
&mut t,
)
.await
.unwrap();
2022-06-04 16:05:18 +02:00
let expected = FullAccount {
login: Login::new(login),
email: Email::new(email),
pass_hash: PassHash::new(&hash),
role: Role::Admin,
customer_id: account.customer_id,
id: account.id,
state: AccountState::Active,
};
t.rollback().await.unwrap();
2022-06-04 16:05:18 +02:00
assert_eq!(account, expected);
}
#[actix::test]
async fn all_accounts() {
testx::db_t_ref!(t);
2022-06-04 16:05:18 +02:00
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;
2022-06-04 16:05:18 +02:00
let v: Vec<FullAccount> = super::all_accounts(AllAccounts, &mut t).await.unwrap();
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
2022-06-04 16:05:18 +02:00
assert!(v.len() >= 3);
}
#[actix::test]
async fn update_account_without_pass() {
testx::db_t_ref!(t);
2022-06-04 16:05:18 +02:00
let original_login: String = fake::faker::internet::en::Username().fake();
let original_email: String = fake::faker::internet::en::FreeEmail().fake();
let original_hash: String = fake::faker::internet::en::Password(10..20).fake();
let original_account = test_create_account(
&mut t,
2022-06-04 16:05:18 +02:00
Some(original_login.clone()),
Some(original_email.clone()),
Some(original_hash.clone()),
)
.await;
let updated_login: String = fake::faker::internet::en::Username().fake();
let updated_email: String = fake::faker::internet::en::FreeEmail().fake();
let updated_account: FullAccount = super::update_account(
UpdateAccount {
2022-06-04 16:05:18 +02:00
id: original_account.id,
email: Email::new(updated_email.clone()),
login: Login::new(updated_login.clone()),
pass_hash: None,
role: Role::Admin,
state: AccountState::Active,
},
&mut t,
)
.await
.unwrap();
2022-06-04 16:05:18 +02:00
let expected = FullAccount {
id: original_account.id,
email: Email::new(updated_email),
login: Login::new(updated_login),
pass_hash: PassHash::new(original_hash),
role: Role::Admin,
customer_id: original_account.customer_id,
state: AccountState::Active,
};
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
2022-06-04 16:05:18 +02:00
assert_ne!(original_account, expected);
assert_eq!(updated_account, expected);
}
#[actix::test]
async fn update_account_with_pass() {
testx::db_t_ref!(t);
2022-06-04 16:05:18 +02:00
let original_login: String = fake::faker::internet::en::Username().fake();
let original_email: String = fake::faker::internet::en::FreeEmail().fake();
let original_hash: String = fake::faker::internet::en::Password(10..20).fake();
let original_account = test_create_account(
&mut t,
2022-06-04 16:05:18 +02:00
Some(original_login.clone()),
Some(original_email.clone()),
Some(original_hash.clone()),
)
.await;
let updated_login: String = fake::faker::internet::en::Username().fake();
let updated_email: String = fake::faker::internet::en::FreeEmail().fake();
let updated_hash: String = fake::faker::internet::en::Password(10..20).fake();
let updated_account: FullAccount = super::update_account(
UpdateAccount {
2022-06-04 16:05:18 +02:00
id: original_account.id,
email: Email::new(updated_email.clone()),
login: Login::new(updated_login.clone()),
pass_hash: Some(PassHash::new(updated_hash.clone())),
role: Role::Admin,
state: AccountState::Active,
},
&mut t,
)
.await
.unwrap();
2022-06-04 16:05:18 +02:00
let expected = FullAccount {
id: original_account.id,
email: Email::new(updated_email),
login: Login::new(updated_login),
pass_hash: PassHash::new(updated_hash),
role: Role::Admin,
customer_id: original_account.customer_id,
state: AccountState::Active,
};
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
2022-06-04 16:05:18 +02:00
assert_ne!(original_account, expected);
assert_eq!(updated_account, expected);
}
#[actix::test]
async fn find() {
testx::db_t_ref!(t);
2022-06-04 16:05:18 +02:00
let account = test_create_account(&mut t, None, None, None).await;
2022-06-04 16:05:18 +02:00
let res: FullAccount = super::find_account(
FindAccount {
2022-06-04 16:05:18 +02:00
account_id: account.id,
},
&mut t,
)
.await
.unwrap();
2022-06-04 16:05:18 +02:00
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
2022-06-04 16:05:18 +02:00
assert_eq!(account, res);
}
#[actix::test]
async fn find_identity_email() {
testx::db_t_ref!(t);
2022-06-04 16:05:18 +02:00
let account = test_create_account(&mut t, None, None, None).await;
2022-06-04 16:05:18 +02:00
let res: FullAccount = super::account_by_identity(
AccountByIdentity {
2022-06-04 16:05:18 +02:00
email: Some(account.email.clone()),
login: None,
},
&mut t,
)
.await
.unwrap();
2022-06-04 16:05:18 +02:00
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
2022-06-04 16:05:18 +02:00
assert_eq!(account, res);
}
#[actix::test]
async fn find_identity_login() {
testx::db_t_ref!(t);
2022-06-04 16:05:18 +02:00
let account = test_create_account(&mut t, None, None, None).await;
2022-06-04 16:05:18 +02:00
let res: FullAccount = super::account_by_identity(
AccountByIdentity {
2022-06-04 16:05:18 +02:00
login: Some(account.login.clone()),
email: None,
},
&mut t,
)
.await
.unwrap();
2022-06-04 16:05:18 +02:00
2022-06-05 21:03:22 +02:00
testx::db_rollback!(t);
2022-06-04 16:05:18 +02:00
assert_eq!(account, res);
}
}