diff --git a/actors/database_manager/src/account_addresses.rs b/actors/database_manager/src/account_addresses.rs index 678e5a3..1ae111c 100644 --- a/actors/database_manager/src/account_addresses.rs +++ b/actors/database_manager/src/account_addresses.rs @@ -27,7 +27,7 @@ pub(crate) async fn account_addresses( ) -> Result> { sqlx::query_as( r#" -SELECT id, name, email, street, city, country, zip, account_id, is_default +SELECT id, name, email, phone, street, city, country, zip, account_id, is_default FROM account_addresses WHERE account_id = $1 "#, @@ -37,7 +37,6 @@ WHERE account_id = $1 .await .map_err(|_| Error::AccountAddresses.into()) } -//// #[derive(actix::Message)] #[rtype(result = "Result")] @@ -59,7 +58,7 @@ pub(crate) async fn find_account_address( ) -> Result { sqlx::query_as( r#" -SELECT id, name, email, street, city, country, zip, account_id, is_default +SELECT id, name, email, phone, street, city, country, zip, account_id, is_default FROM account_addresses WHERE account_id = $1 AND id = $2 "#, @@ -92,7 +91,7 @@ pub(crate) async fn default_account_address( ) -> Result { sqlx::query_as( r#" -SELECT id, name, email, street, city, country, zip, account_id, is_default +SELECT id, name, email, phone, street, city, country, zip, account_id, is_default FROM account_addresses WHERE account_id = $1 AND is_default "#, @@ -108,6 +107,7 @@ WHERE account_id = $1 AND is_default 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, @@ -127,7 +127,7 @@ pub(crate) async fn create_address( msg: CreateAccountAddress, pool: &mut sqlx::Transaction<'_, sqlx::Postgres>, ) -> Result { - if msg.is_default { + if msg.is_default && msg.account_id.is_some() { if let Err(e) = sqlx::query( r#" UPDATE account_addresses @@ -142,20 +142,23 @@ WHERE account_id = $1 log::error!("{}", e); } } + sqlx::query_as( r#" -INSERT INTO account_addresses ( name, email, street, city, country, zip, account_id ) -VALUES ($1, $2, $3, $4, $5, $6, $7) -RETURNING id, name, email, street, city, country, zip, account_id, is_default +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()) @@ -167,6 +170,7 @@ 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, @@ -189,9 +193,9 @@ pub(crate) async fn update_account_address( 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 +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, street, city, country, zip, account_id, is_default +RETURNING id, name, email, phone, street, city, country, zip, account_id, is_default "#, ) .bind(msg.id) @@ -203,7 +207,140 @@ RETURNING id, name, email, street, city, country, zip, account_id, is_default .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: model::Email::new(email), + login: model::Login::new(login), + pass_hash: model::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 = crate::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: model::AddressId::new(1), + 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); + } +} diff --git a/actors/database_manager/src/lib.rs b/actors/database_manager/src/lib.rs index 13a18fe..574a1eb 100644 --- a/actors/database_manager/src/lib.rs +++ b/actors/database_manager/src/lib.rs @@ -196,16 +196,16 @@ impl Actor for Database { /// /// ``` /// # use database_manager::photos::Error; -/// async fn load() { -/// # let pool: sqlx::PgPool::connect("").await.unwrap(); +/// async fn load(pool: sqlx::PgPool) { /// use database_manager::MultiLoad; -/// let t = pool.begin().await.unwrap(); +/// let mut t = pool.begin().await.unwrap(); /// let mut multi = MultiLoad::new( /// &mut t, /// "SELECT id, name FROM products WHERE ", /// " id = " /// ); -/// multi.load(4, vec![1,2,3,4], |_| Error::All.into()); +/// let products: Vec = multi.load(4, vec![1, 2, 3, 4].into_iter(), |_| Error::All.into()) +/// .await.unwrap(); /// t.commit().await.unwrap(); /// } /// ``` diff --git a/actors/order_manager/src/lib.rs b/actors/order_manager/src/lib.rs index 08d8765..9066fd2 100644 --- a/actors/order_manager/src/lib.rs +++ b/actors/order_manager/src/lib.rs @@ -78,6 +78,7 @@ impl OrderManager { pub struct CreateOrderAddress { pub name: model::Name, pub email: model::Email, + pub phone: model::Phone, pub street: model::Street, pub city: model::City, pub country: model::Country, @@ -130,6 +131,7 @@ pub(crate) async fn create_account_order( database_manager::CreateAccountAddress { name: input.name, email: input.email, + phone: input.phone, street: input.street, city: input.city, country: input.country, diff --git a/api/src/routes/public/api_v1/restricted.rs b/api/src/routes/public/api_v1/restricted.rs index 6e24c69..430a9a7 100644 --- a/api/src/routes/public/api_v1/restricted.rs +++ b/api/src/routes/public/api_v1/restricted.rs @@ -285,6 +285,7 @@ pub(crate) async fn create_order( api::OrderAddressInput::Address(api::CreateOrderAddress { name, email, + phone, street, city, country, @@ -292,6 +293,7 @@ pub(crate) async fn create_order( }) => order_manager::OrderAddressInput::Address(order_manager::CreateOrderAddress { name, email: email.clone(), + phone, street, city, country, diff --git a/db-seed/src/main.rs b/db-seed/src/main.rs index 000d9d2..3b3e3d0 100644 --- a/db-seed/src/main.rs +++ b/db-seed/src/main.rs @@ -51,7 +51,6 @@ async fn main() { let db = database_manager::Database::build(config.clone()) .await - .unwrap() .start(); let res = tokio::join!( diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..4ac4682 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env zsh + +psql postgres postgres -c "DROP DATABASE bazzar_test" +psql postgres postgres -c "CREATE DATABASE bazzar_test" +sqlx migrate run --database-url='postgres://postgres@localhost/bazzar_test' +cargo test diff --git a/shared/model/src/api.rs b/shared/model/src/api.rs index c8faaec..b13cd58 100644 --- a/shared/model/src/api.rs +++ b/shared/model/src/api.rs @@ -63,6 +63,7 @@ pub struct AccountAddress { pub id: AddressId, pub name: Name, pub email: Email, + pub phone: Phone, pub street: Street, pub city: City, pub country: Country, @@ -77,6 +78,7 @@ impl From for AccountAddress { id, name, email, + phone, street, city, country, @@ -89,6 +91,7 @@ impl From for AccountAddress { id, name, email, + phone, street, city, country, @@ -502,6 +505,7 @@ pub struct SearchRequest { pub struct CreateOrderAddress { pub name: Name, pub email: Email, + pub phone: Phone, pub street: Street, pub city: City, pub country: Country, diff --git a/shared/model/src/dummy.rs b/shared/model/src/dummy.rs index 4d59031..9171bca 100644 --- a/shared/model/src/dummy.rs +++ b/shared/model/src/dummy.rs @@ -1,4 +1,5 @@ use fake::faker::internet::en::{FreeEmail, Password as FakePass, Username}; +use fake::faker::phone_number::en::PhoneNumber; use fake::Fake; use crate::*; @@ -15,6 +16,12 @@ impl fake::Dummy for Email { } } +impl fake::Dummy for Phone { + fn dummy_with_rng(_config: &T, _rng: &mut R) -> Self { + Self(PhoneNumber().fake()) + } +} + impl fake::Dummy for ProductShortDesc { fn dummy_with_rng(_config: &T, _rng: &mut R) -> Self { use fake::faker::lorem::en::Words; diff --git a/shared/model/src/lib.rs b/shared/model/src/lib.rs index 1002a61..af2b869 100644 --- a/shared/model/src/lib.rs +++ b/shared/model/src/lib.rs @@ -342,7 +342,9 @@ impl Login { #[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", sqlx(transparent))] -#[derive(Serialize, Debug, Clone, Default, Deref, DerefMut, From, Display)] +#[derive( + Serialize, Debug, Clone, Default, PartialOrd, PartialEq, Deref, DerefMut, From, Display, +)] #[serde(transparent)] pub struct Email(String); @@ -356,6 +358,34 @@ impl Email { } } +#[cfg_attr(feature = "db", derive(sqlx::Type))] +#[cfg_attr(feature = "db", sqlx(transparent))] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Default, + PartialOrd, + PartialEq, + Deref, + DerefMut, + From, + Display, +)] +#[serde(transparent)] +pub struct Phone(String); + +impl Phone { + pub fn invalid_empty() -> Self { + Self("".into()) + } + + pub fn new>(s: S) -> Self { + Self(s.into()) + } +} + impl FromStr for Email { type Err = TransformError; @@ -680,7 +710,9 @@ impl PartialEq for Password { #[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", sqlx(transparent))] -#[derive(Serialize, Deserialize, Copy, Clone, Debug, Deref, Display, From)] +#[derive( + Serialize, Deserialize, Copy, Clone, Debug, PartialOrd, PartialEq, Deref, Display, From, +)] #[serde(transparent)] pub struct AccountId(RecordId); @@ -1134,12 +1166,32 @@ pub enum ShippingMethod { #[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", sqlx(transparent))] -#[derive(Serialize, Deserialize, Debug, Copy, Clone, Hash, Deref, Display, From)] +#[derive( + Serialize, Deserialize, Debug, Copy, Clone, Hash, PartialOrd, PartialEq, Deref, Display, From, +)] pub struct AddressId(RecordId); +impl AddressId { + pub fn new(id: RecordId) -> Self { + Self(id) + } +} + #[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", sqlx(transparent))] -#[derive(Serialize, Deserialize, Debug, Clone, Default, Deref, DerefMut, From, Display)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Default, + PartialOrd, + PartialEq, + Deref, + DerefMut, + From, + Display, +)] #[serde(transparent)] pub struct Name(String); @@ -1151,7 +1203,19 @@ impl Name { #[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", sqlx(transparent))] -#[derive(Serialize, Deserialize, Debug, Clone, Default, Deref, DerefMut, From, Display)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Default, + PartialOrd, + PartialEq, + Deref, + DerefMut, + From, + Display, +)] #[serde(transparent)] pub struct Street(String); @@ -1163,7 +1227,19 @@ impl Street { #[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", sqlx(transparent))] -#[derive(Serialize, Deserialize, Debug, Clone, Default, Deref, DerefMut, From, Display)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Default, + PartialOrd, + PartialEq, + Deref, + DerefMut, + From, + Display, +)] #[serde(transparent)] pub struct City(String); @@ -1175,7 +1251,19 @@ impl City { #[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", sqlx(transparent))] -#[derive(Serialize, Deserialize, Debug, Clone, Default, Deref, DerefMut, From, Display)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Default, + PartialOrd, + PartialEq, + Deref, + DerefMut, + From, + Display, +)] #[serde(transparent)] pub struct Country(String); @@ -1187,7 +1275,19 @@ impl Country { #[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", sqlx(transparent))] -#[derive(Serialize, Deserialize, Debug, Clone, Default, Deref, DerefMut, From, Display)] +#[derive( + Serialize, + Deserialize, + Debug, + Clone, + Default, + PartialOrd, + PartialEq, + Deref, + DerefMut, + From, + Display, +)] #[serde(transparent)] pub struct Zip(String); @@ -1199,11 +1299,12 @@ impl Zip { #[cfg_attr(feature = "dummy", derive(fake::Dummy))] #[cfg_attr(feature = "db", derive(sqlx::FromRow))] -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Debug)] pub struct AccountAddress { pub id: AddressId, pub name: Name, pub email: Email, + pub phone: Phone, pub street: Street, pub city: City, pub country: Country,