use crate::{db_async_handler, Result}; #[derive(Debug, Copy, Clone, PartialEq, serde::Serialize, thiserror::Error)] pub enum Error { #[error("Can't load account addresses")] AccountAddresses, #[error("Failed to save account address")] CreateAccountAddress, } #[derive(actix::Message)] #[rtype(result = "Result>")] pub struct AccountAddresses { pub account_id: model::AccountId, } db_async_handler!( AccountAddresses, account_addresses, Vec, inner_account_addresses ); pub(crate) async fn account_addresses( msg: AccountAddresses, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result> { sqlx::query_as( r#" SELECT id, name, email, phone, street, city, country, zip, account_id, is_default FROM account_addresses WHERE account_id = $1 "#, ) .bind(msg.account_id) .fetch_all(pool) .await .map_err(|_| Error::AccountAddresses.into()) } #[derive(actix::Message)] #[rtype(result = "Result")] pub struct FindAccountAddress { pub account_id: model::AccountId, pub address_id: model::AddressId, } db_async_handler!( FindAccountAddress, find_account_address, model::AccountAddress, inner_find_account_address ); pub(crate) async fn find_account_address( msg: FindAccountAddress, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { sqlx::query_as( r#" SELECT id, name, email, phone, street, city, country, zip, account_id, is_default FROM account_addresses WHERE account_id = $1 AND id = $2 "#, ) .bind(msg.account_id) .bind(msg.address_id) .fetch_one(pool) .await .map_err(|_| Error::AccountAddresses.into()) } ///// #[derive(actix::Message)] #[rtype(result = "Result")] pub struct DefaultAccountAddress { pub account_id: model::AccountId, } db_async_handler!( DefaultAccountAddress, default_account_address, model::AccountAddress, inner_default_account_address ); pub(crate) async fn default_account_address( msg: DefaultAccountAddress, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { sqlx::query_as( r#" SELECT id, name, email, phone, street, city, country, zip, account_id, is_default FROM account_addresses WHERE account_id = $1 AND is_default "#, ) .bind(msg.account_id) .fetch_one(pool) .await .map_err(|_| Error::AccountAddresses.into()) } #[derive(actix::Message)] #[rtype(result = "Result")] pub struct CreateAccountAddress { pub name: model::Name, pub email: model::Email, pub phone: model::Phone, pub street: model::Street, pub city: model::City, pub country: model::Country, pub zip: model::Zip, pub account_id: Option, pub is_default: bool, } db_async_handler!( CreateAccountAddress, create_address, model::AccountAddress, inner_create_address ); pub(crate) async fn create_address( msg: CreateAccountAddress, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { if msg.is_default && msg.account_id.is_some() { if let Err(e) = sqlx::query( r#" UPDATE account_addresses SET is_default = FALSE WHERE account_id = $1 "#, ) .bind(msg.account_id) .fetch_all(&mut *pool) .await { log::error!("{}", e); } } sqlx::query_as( r#" INSERT INTO account_addresses ( name, email, phone, street, city, country, zip, account_id ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, name, email, phone, street, city, country, zip, account_id, is_default "#, ) .bind(msg.name) .bind(msg.email) .bind(msg.phone) .bind(msg.street) .bind(msg.city) .bind(msg.country) .bind(msg.zip) .bind(msg.account_id) .bind(msg.is_default) .fetch_one(pool) .await .map_err(|_| Error::CreateAccountAddress.into()) } #[derive(actix::Message)] #[rtype(result = "Result")] pub struct UpdateAccountAddress { pub id: model::AddressId, pub name: model::Name, pub email: model::Email, pub phone: model::Phone, pub street: model::Street, pub city: model::City, pub country: model::Country, pub zip: model::Zip, pub account_id: model::AccountId, pub is_default: bool, } db_async_handler!( UpdateAccountAddress, update_account_address, model::AccountAddress, inner_update_account_address ); pub(crate) async fn update_account_address( msg: UpdateAccountAddress, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { sqlx::query_as( r#" UPDATE account_addresses SET name = $2, email = $3, street = $4, city = $5, country = $6, zip = $7, account_id = $8, is_default = $9, phone = $10 WHERE id = $1 RETURNING id, name, email, phone, street, city, country, zip, account_id, is_default "#, ) .bind(msg.id) .bind(msg.name) .bind(msg.email) .bind(msg.street) .bind(msg.city) .bind(msg.country) .bind(msg.zip) .bind(msg.account_id) .bind(msg.is_default) .bind(msg.phone) .fetch_one(pool) .await .map_err(|_| Error::CreateAccountAddress.into()) } #[cfg(test)] mod test { use actix::{Actor, Addr}; use config::*; use fake::Fake; use model::*; use crate::*; pub struct NoOpts; impl UpdateConfig for NoOpts {} async fn test_create_account(db: Addr) -> FullAccount { 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(); db.send(CreateAccount { email: Email::new(email), login: Login::new(login), pass_hash: PassHash::new(hash), role: Role::Admin, }) .await .unwrap() .unwrap() } #[actix::test] async fn full_check() { let config = config::default_load(&mut NoOpts); config .lock() .database_mut() .set_url("postgres://postgres@localhost/bazzar_test"); let db = Database::build(config).await.start(); // account let account = test_create_account(db.clone()).await; // address let mut address: AccountAddress = { let name: String = fake::faker::name::en::Name().fake(); let email: String = fake::faker::internet::en::FreeEmail().fake(); let phone: String = fake::faker::phone_number::en::PhoneNumber().fake(); let street: String = fake::faker::address::en::StreetName().fake(); let city: String = fake::faker::address::en::CityName().fake(); let country: String = fake::faker::address::en::CountryName().fake(); let zip: String = fake::faker::address::en::ZipCode().fake(); let account_id = Some(account.id); let is_default: bool = fake::faker::boolean::en::Boolean(6).fake(); let address = db .send(CreateAccountAddress { name: model::Name::new(name.clone()), email: model::Email::new(email.clone()), phone: model::Phone::new(phone.clone()), street: model::Street::new(street.clone()), city: model::City::new(city.clone()), country: model::Country::new(country.clone()), zip: model::Zip::new(zip.clone()), account_id: account_id.clone(), is_default: is_default.clone(), }) .await .unwrap() .unwrap(); assert_eq!( address, model::AccountAddress { id: address.id, name: model::Name::new(name.clone()), email: model::Email::new(email.clone()), phone: model::Phone::new(phone.clone()), street: model::Street::new(street.clone()), city: model::City::new(city.clone()), country: model::Country::new(country.clone()), zip: model::Zip::new(zip.clone()), account_id: account.id, is_default, } ); address }; let found = db .send(FindAccountAddress { account_id: account.id, address_id: address.id, }) .await .unwrap() .unwrap(); assert_eq!(found, address); let changed = db .send(UpdateAccountAddress { id: address.id, name: address.name.clone(), email: address.email.clone(), phone: address.phone.clone(), street: address.street.clone(), city: address.city.clone(), country: address.country.clone(), zip: address.zip.clone(), account_id: address.account_id.clone(), is_default: true, }) .await .unwrap() .unwrap(); address.is_default = true; assert_eq!(changed, address); let default_address = db .send(DefaultAccountAddress { account_id: account.id, }) .await .unwrap() .unwrap(); assert_eq!(default_address, address); } }