2022-11-04 18:40:14 +01:00
|
|
|
use model::{AccountId, AccountState, Email, FullAccount, Login, PassHash, Role};
|
2022-04-15 17:04:23 +02:00
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
pub type Result<T> = std::result::Result<T, Error>;
|
2022-04-15 17:04:23 +02:00
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)]
|
2022-04-15 17:04:23 +02:00
|
|
|
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,
|
2022-04-16 06:58:48 +02:00
|
|
|
#[error("Failed to load all accounts")]
|
|
|
|
All,
|
2022-06-04 16:05:18 +02:00
|
|
|
#[error("Can't update account")]
|
|
|
|
CantUpdate,
|
2022-04-16 06:58:48 +02:00
|
|
|
}
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
#[derive(Debug)]
|
2022-04-16 06:58:48 +02:00
|
|
|
pub struct AllAccounts;
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
impl AllAccounts {
|
|
|
|
pub async fn run(
|
|
|
|
_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
|
2022-04-16 06:58:48 +02:00
|
|
|
FROM accounts
|
|
|
|
"#,
|
2022-11-04 18:40:14 +01:00
|
|
|
)
|
|
|
|
.fetch_all(pool)
|
|
|
|
.await
|
|
|
|
.map_err(|e| {
|
|
|
|
tracing::error!("{e:?}");
|
|
|
|
Error::All
|
|
|
|
})
|
|
|
|
}
|
2022-04-15 17:04:23 +02:00
|
|
|
}
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
#[derive(Debug)]
|
2022-04-15 17:04:23 +02:00
|
|
|
pub struct CreateAccount {
|
|
|
|
pub email: Email,
|
|
|
|
pub login: Login,
|
|
|
|
pub pass_hash: PassHash,
|
|
|
|
pub role: Role,
|
|
|
|
}
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
impl CreateAccount {
|
|
|
|
pub async fn run(
|
|
|
|
self,
|
|
|
|
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
|
|
|
) -> Result<FullAccount> {
|
|
|
|
sqlx::query_as(
|
|
|
|
r#"
|
2022-04-15 17:04:23 +02:00
|
|
|
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
|
|
|
"#,
|
2022-11-04 18:40:14 +01:00
|
|
|
)
|
|
|
|
.bind(self.login)
|
|
|
|
.bind(self.email)
|
|
|
|
.bind(self.role)
|
|
|
|
.bind(self.pass_hash)
|
|
|
|
.fetch_one(pool)
|
|
|
|
.await
|
|
|
|
.map_err(|e| {
|
|
|
|
tracing::error!("{e:?}");
|
|
|
|
Error::CantCreate
|
|
|
|
})
|
|
|
|
}
|
2022-04-18 08:22:51 +02:00
|
|
|
}
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
#[derive(Debug)]
|
2022-04-18 08:22:51 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
impl UpdateAccount {
|
|
|
|
pub async fn run(
|
|
|
|
self,
|
|
|
|
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
|
|
|
) -> Result<FullAccount> {
|
|
|
|
match self.pass_hash {
|
|
|
|
Some(hash) => sqlx::query_as(
|
|
|
|
r#"
|
2022-04-19 16:49:30 +02:00
|
|
|
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-11-04 18:40:14 +01:00
|
|
|
)
|
|
|
|
.bind(self.id)
|
|
|
|
.bind(self.login)
|
|
|
|
.bind(self.email)
|
|
|
|
.bind(self.role)
|
|
|
|
.bind(hash)
|
|
|
|
.bind(self.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-15 17:04:23 +02:00
|
|
|
"#,
|
2022-11-04 18:40:14 +01:00
|
|
|
)
|
|
|
|
.bind(self.id)
|
|
|
|
.bind(self.login)
|
|
|
|
.bind(self.email)
|
|
|
|
.bind(self.role)
|
|
|
|
.bind(self.state),
|
|
|
|
}
|
|
|
|
.fetch_one(pool)
|
|
|
|
.await
|
|
|
|
.map_err(|e| {
|
|
|
|
tracing::error!("{e:?}");
|
|
|
|
Error::CantUpdate
|
|
|
|
})
|
2022-04-19 16:49:30 +02:00
|
|
|
}
|
2022-04-15 17:04:23 +02:00
|
|
|
}
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
#[derive(Debug)]
|
2022-04-15 17:04:23 +02:00
|
|
|
pub struct FindAccount {
|
|
|
|
pub account_id: AccountId,
|
|
|
|
}
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
impl FindAccount {
|
|
|
|
pub async fn run(
|
|
|
|
self,
|
|
|
|
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
|
2022-04-15 17:04:23 +02:00
|
|
|
FROM accounts
|
|
|
|
WHERE id = $1
|
|
|
|
"#,
|
2022-11-04 18:40:14 +01:00
|
|
|
)
|
|
|
|
.bind(self.account_id)
|
|
|
|
.fetch_one(pool)
|
|
|
|
.await
|
|
|
|
.map_err(|e| {
|
|
|
|
tracing::error!("{e:?}");
|
|
|
|
Error::NotExists
|
|
|
|
})
|
|
|
|
}
|
2022-04-15 17:04:23 +02:00
|
|
|
}
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
#[derive(Debug)]
|
2022-04-15 17:04:23 +02:00
|
|
|
pub struct AccountByIdentity {
|
|
|
|
pub login: Option<Login>,
|
|
|
|
pub email: Option<Email>,
|
|
|
|
}
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
impl AccountByIdentity {
|
|
|
|
pub async fn run(
|
|
|
|
self,
|
|
|
|
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
|
|
|
) -> Result<FullAccount> {
|
|
|
|
match (self.login, self.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
|
2022-04-15 17:04:23 +02:00
|
|
|
FROM accounts
|
|
|
|
WHERE login = $1
|
|
|
|
"#,
|
2022-11-04 18:40:14 +01:00
|
|
|
)
|
|
|
|
.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
|
2022-04-15 17:04:23 +02:00
|
|
|
FROM accounts
|
|
|
|
WHERE email = $1
|
|
|
|
"#,
|
2022-11-04 18:40:14 +01:00
|
|
|
)
|
|
|
|
.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
|
2022-04-15 17:04:23 +02:00
|
|
|
FROM accounts
|
|
|
|
WHERE login = $1 AND email = $2
|
|
|
|
"#,
|
2022-11-04 18:40:14 +01:00
|
|
|
)
|
|
|
|
.bind(login)
|
|
|
|
.bind(email),
|
|
|
|
_ => return Err(Error::NoIdentity),
|
|
|
|
}
|
|
|
|
.fetch_one(pool)
|
|
|
|
.await
|
|
|
|
.map_err(|e| {
|
|
|
|
tracing::error!("{e:?}");
|
|
|
|
Error::CantCreate
|
|
|
|
})
|
2022-04-15 17:04:23 +02:00
|
|
|
}
|
|
|
|
}
|
2022-06-04 16:05:18 +02:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use config::UpdateConfig;
|
|
|
|
use fake::Fake;
|
|
|
|
use model::*;
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
use super::*;
|
2022-06-04 16:05:18 +02:00
|
|
|
|
|
|
|
pub struct NoOpts;
|
|
|
|
|
|
|
|
impl UpdateConfig for NoOpts {}
|
|
|
|
|
|
|
|
async fn test_create_account(
|
2022-06-05 17:44:47 +02:00
|
|
|
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());
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
CreateAccount {
|
|
|
|
email: Email::new(email),
|
|
|
|
login: Login::new(login),
|
|
|
|
pass_hash: PassHash::new(hash),
|
|
|
|
role: Role::Admin,
|
|
|
|
}
|
|
|
|
.run(t)
|
2022-06-04 16:05:18 +02:00
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[actix::test]
|
|
|
|
async fn create_account() {
|
2022-06-09 15:28:15 +02:00
|
|
|
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();
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
let account: FullAccount = CreateAccount {
|
|
|
|
email: Email::new(&email),
|
|
|
|
login: Login::new(&login),
|
|
|
|
pass_hash: PassHash::new(&hash),
|
|
|
|
role: Role::Admin,
|
|
|
|
}
|
|
|
|
.run(&mut t)
|
2022-06-05 17:44:47 +02:00
|
|
|
.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,
|
|
|
|
};
|
|
|
|
|
2022-06-05 17:44:47 +02:00
|
|
|
t.rollback().await.unwrap();
|
2022-06-04 16:05:18 +02:00
|
|
|
assert_eq!(account, expected);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[actix::test]
|
|
|
|
async fn all_accounts() {
|
2022-06-09 15:28:15 +02:00
|
|
|
testx::db_t_ref!(t);
|
2022-06-04 16:05:18 +02:00
|
|
|
|
2022-06-05 17:44:47 +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
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
let v: Vec<FullAccount> = AllAccounts.run(&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() {
|
2022-06-09 15:28:15 +02:00
|
|
|
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(
|
2022-06-05 17:44:47 +02:00
|
|
|
&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();
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
let updated_account: FullAccount = UpdateAccount {
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
.run(&mut t)
|
2022-06-05 17:44:47 +02:00
|
|
|
.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() {
|
2022-06-09 15:28:15 +02:00
|
|
|
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(
|
2022-06-05 17:44:47 +02:00
|
|
|
&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();
|
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
let updated_account: FullAccount = UpdateAccount {
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
.run(&mut t)
|
2022-06-05 17:44:47 +02:00
|
|
|
.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() {
|
2022-06-09 15:28:15 +02:00
|
|
|
testx::db_t_ref!(t);
|
2022-06-04 16:05:18 +02:00
|
|
|
|
2022-06-05 17:44:47 +02:00
|
|
|
let account = test_create_account(&mut t, None, None, None).await;
|
2022-06-04 16:05:18 +02:00
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
let res: FullAccount = FindAccount {
|
|
|
|
account_id: account.id,
|
|
|
|
}
|
|
|
|
.run(&mut t)
|
2022-06-05 17:44:47 +02:00
|
|
|
.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() {
|
2022-06-09 15:28:15 +02:00
|
|
|
testx::db_t_ref!(t);
|
2022-06-04 16:05:18 +02:00
|
|
|
|
2022-06-05 17:44:47 +02:00
|
|
|
let account = test_create_account(&mut t, None, None, None).await;
|
2022-06-04 16:05:18 +02:00
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
let res: FullAccount = AccountByIdentity {
|
|
|
|
email: Some(account.email.clone()),
|
|
|
|
login: None,
|
|
|
|
}
|
|
|
|
.run(&mut t)
|
2022-06-05 17:44:47 +02:00
|
|
|
.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() {
|
2022-06-09 15:28:15 +02:00
|
|
|
testx::db_t_ref!(t);
|
2022-06-04 16:05:18 +02:00
|
|
|
|
2022-06-05 17:44:47 +02:00
|
|
|
let account = test_create_account(&mut t, None, None, None).await;
|
2022-06-04 16:05:18 +02:00
|
|
|
|
2022-11-04 18:40:14 +01:00
|
|
|
let res: FullAccount = AccountByIdentity {
|
|
|
|
login: Some(account.login.clone()),
|
|
|
|
email: None,
|
|
|
|
}
|
|
|
|
.run(&mut t)
|
2022-06-05 17:44:47 +02:00
|
|
|
.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);
|
|
|
|
}
|
|
|
|
}
|