Seed products with photos
82
Cargo.lock
generated
@ -413,6 +413,15 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler"
|
name = "adler"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@ -573,6 +582,21 @@ dependencies = [
|
|||||||
"rand",
|
"rand",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.65"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base-x"
|
name = "base-x"
|
||||||
version = "0.2.10"
|
version = "0.2.10"
|
||||||
@ -607,7 +631,6 @@ dependencies = [
|
|||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-web-httpauth",
|
"actix-web-httpauth",
|
||||||
"actix-web-opentelemetry",
|
"actix-web-opentelemetry",
|
||||||
"argon2",
|
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"cart_manager",
|
"cart_manager",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -620,6 +643,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"gumdrop",
|
"gumdrop",
|
||||||
|
"human-panic",
|
||||||
"jemallocator",
|
"jemallocator",
|
||||||
"log",
|
"log",
|
||||||
"messagebus",
|
"messagebus",
|
||||||
@ -627,10 +651,8 @@ dependencies = [
|
|||||||
"oauth2",
|
"oauth2",
|
||||||
"order_manager",
|
"order_manager",
|
||||||
"parking_lot 0.12.0",
|
"parking_lot 0.12.0",
|
||||||
"password-hash",
|
|
||||||
"payment_manager",
|
"payment_manager",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"rand_core",
|
|
||||||
"search_manager",
|
"search_manager",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -1116,10 +1138,15 @@ dependencies = [
|
|||||||
"database_manager",
|
"database_manager",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"fake",
|
"fake",
|
||||||
|
"fs_manager",
|
||||||
|
"human-panic",
|
||||||
"log",
|
"log",
|
||||||
"model",
|
"model",
|
||||||
|
"password-hash",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"rand",
|
"rand",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1519,6 +1546,12 @@ dependencies = [
|
|||||||
"polyval",
|
"polyval",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.26.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git2"
|
name = "git2"
|
||||||
version = "0.13.25"
|
version = "0.13.25"
|
||||||
@ -1751,6 +1784,21 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "human-panic"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39f357a500abcbd7c5f967c1d45c8838585b36743823b9d43488f24850534e36"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"os_type",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"termcolor",
|
||||||
|
"toml",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "humansize"
|
name = "humansize"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@ -2184,10 +2232,14 @@ dependencies = [
|
|||||||
name = "model"
|
name = "model"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"argon2",
|
||||||
"chrono",
|
"chrono",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"fake",
|
"fake",
|
||||||
|
"log",
|
||||||
|
"password-hash",
|
||||||
"rand",
|
"rand",
|
||||||
|
"rand_core",
|
||||||
"serde",
|
"serde",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
@ -2315,6 +2367,15 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.28.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.10.0"
|
version = "1.10.0"
|
||||||
@ -2412,6 +2473,15 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_type"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3df761f6470298359f84fcfb60d86db02acc22c251c37265c07a3d1057d2389"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
@ -2887,6 +2957,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -3,8 +3,7 @@ use fake::Fake;
|
|||||||
use model::{AccountId, AccountState, Email, FullAccount, Login, PassHash, Role};
|
use model::{AccountId, AccountState, Email, FullAccount, Login, PassHash, Role};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use super::Result;
|
use crate::{db_async_handler, Result};
|
||||||
use crate::db_async_handler;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -40,12 +39,10 @@ FROM accounts
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[derive(actix::Message)]
|
#[derive(actix::Message, Debug)]
|
||||||
#[rtype(result = "Result<FullAccount>")]
|
#[rtype(result = "Result<FullAccount>")]
|
||||||
pub struct CreateAccount {
|
pub struct CreateAccount {
|
||||||
#[cfg_attr(feature = "dummy", dummy("fake::faker::internet::en::FreeEmail"))]
|
|
||||||
pub email: Email,
|
pub email: Email,
|
||||||
#[cfg_attr(feature = "dummy", dummy("fake::faker::internet::en::Username"))]
|
|
||||||
pub login: Login,
|
pub login: Login,
|
||||||
pub pass_hash: PassHash,
|
pub pass_hash: PassHash,
|
||||||
pub role: Role,
|
pub role: Role,
|
||||||
|
@ -4,12 +4,40 @@ use crate::Result;
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Failed to create photo")]
|
#[error("Failed to create photo")]
|
||||||
Create,
|
Create,
|
||||||
|
#[error("Failed to fetch all photo")]
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(actix::Message)]
|
||||||
|
#[rtype(result = "Result<Vec<model::Photo>>")]
|
||||||
|
pub struct AllPhotos;
|
||||||
|
|
||||||
|
crate::db_async_handler!(AllPhotos, all_photos, Vec<model::Photo>, inner_all_photos);
|
||||||
|
|
||||||
|
pub(crate) async fn all_photos<'e, E>(_msg: AllPhotos, pool: E) -> Result<Vec<model::Photo>>
|
||||||
|
where
|
||||||
|
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
||||||
|
{
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT id, local_path, file_name
|
||||||
|
FROM photos
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
crate::Error::Photo(Error::All)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(actix::Message)]
|
#[derive(actix::Message)]
|
||||||
#[rtype(result = "Result<model::Photo>")]
|
#[rtype(result = "Result<model::Photo>")]
|
||||||
pub struct CreatePhoto {
|
pub struct CreatePhoto {
|
||||||
|
/// Local FILE path
|
||||||
pub local_path: model::LocalPath,
|
pub local_path: model::LocalPath,
|
||||||
|
/// Only file name, this part should be also included in `local_path`
|
||||||
pub file_name: model::FileName,
|
pub file_name: model::FileName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,2 +1,79 @@
|
|||||||
|
use crate::{db_async_handler, Result};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {}
|
pub enum Error {
|
||||||
|
#[error("Failed to attach photo to product")]
|
||||||
|
Create,
|
||||||
|
#[error("Failed to load all product photos")]
|
||||||
|
All,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(actix::Message)]
|
||||||
|
#[rtype(result = "Result<Vec<model::ProductPhoto>>")]
|
||||||
|
pub struct AllProductPhotos;
|
||||||
|
|
||||||
|
crate::db_async_handler!(
|
||||||
|
AllProductPhotos,
|
||||||
|
all_product_photos,
|
||||||
|
Vec<model::ProductPhoto>,
|
||||||
|
inner_all_product_photos
|
||||||
|
);
|
||||||
|
|
||||||
|
pub(crate) async fn all_product_photos<'e, E>(
|
||||||
|
_msg: AllProductPhotos,
|
||||||
|
pool: E,
|
||||||
|
) -> Result<Vec<model::ProductPhoto>>
|
||||||
|
where
|
||||||
|
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
||||||
|
{
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT id, product_id, photo_id
|
||||||
|
FROM product_photos
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
crate::Error::ProductPhoto(Error::All)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(actix::Message)]
|
||||||
|
#[rtype(result = "Result<model::ProductPhoto>")]
|
||||||
|
pub struct CreateProductPhoto {
|
||||||
|
pub product_id: model::ProductId,
|
||||||
|
pub photo_id: model::PhotoId,
|
||||||
|
}
|
||||||
|
|
||||||
|
db_async_handler!(
|
||||||
|
CreateProductPhoto,
|
||||||
|
create_product_photo,
|
||||||
|
model::ProductPhoto,
|
||||||
|
inner_create_product_photo
|
||||||
|
);
|
||||||
|
|
||||||
|
pub(crate) async fn create_product_photo<'e, E>(
|
||||||
|
msg: CreateProductPhoto,
|
||||||
|
pool: E,
|
||||||
|
) -> Result<model::ProductPhoto>
|
||||||
|
where
|
||||||
|
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
|
||||||
|
{
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
INSERT INTO product_photos(product_id, photo_id)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
RETURNING id, product_id, photo_id
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(msg.product_id)
|
||||||
|
.bind(msg.photo_id)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("{:?}", e);
|
||||||
|
crate::Error::ProductPhoto(Error::Create)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -53,7 +53,7 @@ FROM products
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
#[derive(Message)]
|
#[derive(Message, Debug)]
|
||||||
#[rtype(result = "Result<model::Product>")]
|
#[rtype(result = "Result<model::Product>")]
|
||||||
pub struct CreateProduct {
|
pub struct CreateProduct {
|
||||||
pub name: ProductName,
|
pub name: ProductName,
|
||||||
|
@ -21,6 +21,54 @@ macro_rules! fs_async_handler {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! query_fs {
|
||||||
|
($fs: expr, $msg: expr, default $fail: expr) => {
|
||||||
|
match $fs.send($msg).await {
|
||||||
|
Ok(Ok(r)) => r,
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("{e}");
|
||||||
|
$fail
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
$fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($fs: expr, $msg: expr, panic) => {
|
||||||
|
match $fs.send($msg).await {
|
||||||
|
Ok(Ok(r)) => r,
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("{e}");
|
||||||
|
panic!("{:?}", e);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
panic!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($fs: expr, $msg: expr, $fail: expr) => {
|
||||||
|
$crate::query_db!($fs, $msg, $fail, $fail)
|
||||||
|
};
|
||||||
|
|
||||||
|
($fs: expr, $msg: expr, $db_fail: expr, $act_fail: expr) => {
|
||||||
|
match $fs.send($msg).await {
|
||||||
|
Ok(Ok(r)) => r,
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("{e}");
|
||||||
|
return Err($db_fail);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
return Err($act_fail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Can't access file system. Please check privileges")]
|
#[error("Can't access file system. Please check privileges")]
|
||||||
@ -98,7 +146,9 @@ pub(crate) async fn remove_file(msg: RemoveFile, config: SharedAppConfig) -> Res
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct WriteResult {
|
pub struct WriteResult {
|
||||||
pub file_name: FileName,
|
/// Unique file name created with UUID and original file extension
|
||||||
|
pub unique_name: FileName,
|
||||||
|
/// Path to file in local storage
|
||||||
pub local_path: LocalPath,
|
pub local_path: LocalPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,6 +167,8 @@ pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Resul
|
|||||||
mut stream,
|
mut stream,
|
||||||
} = msg;
|
} = msg;
|
||||||
|
|
||||||
|
log::debug!("Writing file {:?}", file_name);
|
||||||
|
|
||||||
let p = std::path::Path::new(&file_name);
|
let p = std::path::Path::new(&file_name);
|
||||||
let ext = p
|
let ext = p
|
||||||
.file_name()
|
.file_name()
|
||||||
@ -126,7 +178,7 @@ pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Resul
|
|||||||
.and_then(OsStr::to_str)
|
.and_then(OsStr::to_str)
|
||||||
.map(String::from);
|
.map(String::from);
|
||||||
|
|
||||||
let file_name = format!(
|
let unique_name = format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
uuid::Uuid::new_v4(),
|
uuid::Uuid::new_v4(),
|
||||||
ext.map(|s| format!(".{s}")).unwrap_or_default()
|
ext.map(|s| format!(".{s}")).unwrap_or_default()
|
||||||
@ -139,21 +191,34 @@ pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Resul
|
|||||||
|
|
||||||
let path = std::path::PathBuf::new()
|
let path = std::path::PathBuf::new()
|
||||||
.join(&output_path)
|
.join(&output_path)
|
||||||
.join(&file_name);
|
.join(&unique_name);
|
||||||
|
log::debug!(
|
||||||
|
"File {:?} will be written as {:?} at {:?}",
|
||||||
|
file_name,
|
||||||
|
unique_name,
|
||||||
|
path
|
||||||
|
);
|
||||||
let mut file = match std::fs::File::create(&path) {
|
let mut file = match std::fs::File::create(&path) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => return Err(Error::CantWrite(e)),
|
Err(e) => return Err(Error::CantWrite(e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut counter = 0;
|
||||||
while let Some(b) = stream.recv().await {
|
while let Some(b) = stream.recv().await {
|
||||||
|
counter += 1;
|
||||||
|
if counter % 100_000 == 0 {
|
||||||
|
log::debug!("Wrote {} for {:?}", counter, file_name);
|
||||||
|
}
|
||||||
match file.write(&[b]) {
|
match file.write(&[b]) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::StorageFull => return Err(Error::NoSpace),
|
Err(e) if e.kind() == std::io::ErrorKind::StorageFull => return Err(Error::NoSpace),
|
||||||
Err(e) => return Err(Error::CantWrite(e)),
|
Err(e) => return Err(Error::CantWrite(e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
log::debug!("File {:?} successfully written", unique_name);
|
||||||
|
|
||||||
Ok(WriteResult {
|
Ok(WriteResult {
|
||||||
file_name: FileName::from(file_name),
|
unique_name: FileName::from(unique_name),
|
||||||
local_path: LocalPath::from(path.to_str().unwrap_or_default().to_string()),
|
local_path: LocalPath::from(path.to_str().unwrap_or_default().to_string()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ search_manager = { path = "../actors/search_manager" }
|
|||||||
token_manager = { path = "../actors/token_manager" }
|
token_manager = { path = "../actors/token_manager" }
|
||||||
fs_manager = { path = "../actors/fs_manager" }
|
fs_manager = { path = "../actors/fs_manager" }
|
||||||
|
|
||||||
|
human-panic = { version = "1.0.3" }
|
||||||
|
|
||||||
actix = { version = "0.13", features = [] }
|
actix = { version = "0.13", features = [] }
|
||||||
actix-rt = { version = "2.7", features = [] }
|
actix-rt = { version = "2.7", features = [] }
|
||||||
actix-web = { version = "4.0", features = [] }
|
actix-web = { version = "4.0", features = [] }
|
||||||
@ -56,10 +58,6 @@ dotenv = { version = "0.15", features = [] }
|
|||||||
derive_more = { version = "0.99", features = [] }
|
derive_more = { version = "0.99", features = [] }
|
||||||
parking_lot = { version = "0.12", features = [] }
|
parking_lot = { version = "0.12", features = [] }
|
||||||
|
|
||||||
password-hash = { version = "0.4", features = ["alloc"] }
|
|
||||||
argon2 = { version = "0.4", features = ["parallel", "password-hash"] }
|
|
||||||
rand_core = { version = "0.6", features = ["std"] }
|
|
||||||
|
|
||||||
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 = [] }
|
||||||
|
@ -1,25 +1 @@
|
|||||||
use argon2::{Algorithm, Argon2, Params, Version};
|
pub mod order_state;
|
||||||
use model::Password;
|
|
||||||
use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
|
|
||||||
|
|
||||||
use crate::PassHash;
|
|
||||||
|
|
||||||
mod order_state;
|
|
||||||
|
|
||||||
pub fn encrypt_password(pass: &Password, salt: &SaltString) -> password_hash::Result<String> {
|
|
||||||
log::debug!("Hashing password {:?}", pass);
|
|
||||||
Ok(
|
|
||||||
Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default())
|
|
||||||
.hash_password(pass.as_bytes(), &salt)?
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validate_password(pass: &Password, pass_hash: &PassHash) -> password_hash::Result<()> {
|
|
||||||
log::debug!("Validating password {:?} {:?}", pass, pass_hash);
|
|
||||||
|
|
||||||
Argon2::default().verify_password(
|
|
||||||
pass.as_bytes(),
|
|
||||||
&PasswordHash::new(pass_hash.as_str()).expect("Invalid hashed password"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -11,16 +11,13 @@ use actix_web::{App, HttpServer};
|
|||||||
use config::UpdateConfig;
|
use config::UpdateConfig;
|
||||||
use email_manager::TestMail;
|
use email_manager::TestMail;
|
||||||
use jemallocator::Jemalloc;
|
use jemallocator::Jemalloc;
|
||||||
use model::{Email, Login, PassHash, Password, Role};
|
use model::{Email, Encrypt, Login, PassHash, Password, Role};
|
||||||
use opts::{
|
use opts::{
|
||||||
Command, CreateAccountCmd, CreateAccountOpts, GenerateHashOpts, MigrateOpts, Opts, ServerOpts,
|
Command, CreateAccountCmd, CreateAccountOpts, GenerateHashOpts, MigrateOpts, Opts, ServerOpts,
|
||||||
TestMailerOpts,
|
TestMailerOpts,
|
||||||
};
|
};
|
||||||
use password_hash::SaltString;
|
|
||||||
use validator::{validate_email, validate_length};
|
use validator::{validate_email, validate_length};
|
||||||
|
|
||||||
use crate::logic::encrypt_password;
|
|
||||||
|
|
||||||
pub mod logic;
|
pub mod logic;
|
||||||
mod opts;
|
mod opts;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
@ -119,9 +116,7 @@ async fn migrate(opts: MigrateOpts) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn generate_hash(_opts: GenerateHashOpts) -> Result<()> {
|
async fn generate_hash(_opts: GenerateHashOpts) -> Result<()> {
|
||||||
use argon2::password_hash::rand_core::OsRng;
|
model::print_hash();
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
|
||||||
println!("{salt}");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +159,9 @@ async fn create_account(opts: CreateAccountOpts) -> Result<()> {
|
|||||||
if pass.trim().is_empty() {
|
if pass.trim().is_empty() {
|
||||||
panic!("Password cannot be empty!");
|
panic!("Password cannot be empty!");
|
||||||
}
|
}
|
||||||
let hash = encrypt_password(&Password::from(pass), &config.lock().web().pass_salt()).unwrap();
|
let hash = Password::from(pass)
|
||||||
|
.encrypt(&config.lock().web().pass_salt())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
db.send(database_manager::CreateAccount {
|
db.send(database_manager::CreateAccount {
|
||||||
email: Email::from(opts.email),
|
email: Email::from(opts.email),
|
||||||
@ -204,6 +201,8 @@ async fn test_mailer(opts: TestMailerOpts) -> Result<()> {
|
|||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
human_panic::setup_panic!();
|
||||||
|
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
|
@ -6,10 +6,9 @@ use actix_web::web::{scope, Data, Json, ServiceConfig};
|
|||||||
use actix_web::{delete, get, post, HttpResponse};
|
use actix_web::{delete, get, post, HttpResponse};
|
||||||
use config::SharedAppConfig;
|
use config::SharedAppConfig;
|
||||||
use database_manager::{query_db, Database};
|
use database_manager::{query_db, Database};
|
||||||
use model::{Account, Email, Login, PassHash, Password, PasswordConfirmation, Role};
|
use model::{Account, Email, Encrypt, Login, PassHash, Password, PasswordConfirmation, Role};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::logic::encrypt_password;
|
|
||||||
use crate::routes;
|
use crate::routes;
|
||||||
use crate::routes::{RequireLogin, Result};
|
use crate::routes::{RequireLogin, Result};
|
||||||
|
|
||||||
@ -79,7 +78,7 @@ async fn sign_in(
|
|||||||
},
|
},
|
||||||
routes::Error::Unauthorized
|
routes::Error::Unauthorized
|
||||||
);
|
);
|
||||||
if let Err(e) = crate::logic::validate_password(&payload.password, &user.pass_hash) {
|
if let Err(e) = payload.password.validate(&user.pass_hash) {
|
||||||
log::error!("Password validation failed. {}", e);
|
log::error!("Password validation failed. {}", e);
|
||||||
Err(routes::Error::Unauthorized)
|
Err(routes::Error::Unauthorized)
|
||||||
} else {
|
} else {
|
||||||
@ -126,7 +125,7 @@ async fn register(
|
|||||||
response.errors.push(RegisterError::PasswordDiffer);
|
response.errors.push(RegisterError::PasswordDiffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hash = match encrypt_password(&input.password, &config.lock().web().pass_salt()) {
|
let hash = match input.password.encrypt(&config.lock().web().pass_salt()) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
|
@ -4,11 +4,11 @@ use actix_web::web::{Data, Json, ServiceConfig};
|
|||||||
use actix_web::{get, patch, post, HttpResponse};
|
use actix_web::{get, patch, post, HttpResponse};
|
||||||
use config::SharedAppConfig;
|
use config::SharedAppConfig;
|
||||||
use database_manager::Database;
|
use database_manager::Database;
|
||||||
use model::{AccountId, AccountState, PasswordConfirmation};
|
use model::{AccountId, AccountState, Encrypt, PasswordConfirmation};
|
||||||
|
|
||||||
use crate::routes::admin::Error;
|
use crate::routes::admin::Error;
|
||||||
use crate::routes::RequireLogin;
|
use crate::routes::RequireLogin;
|
||||||
use crate::{admin_send_db, encrypt_password, routes, Email, Login, PassHash, Password, Role};
|
use crate::{admin_send_db, routes, Email, Login, PassHash, Password, Role};
|
||||||
|
|
||||||
#[get("/accounts")]
|
#[get("/accounts")]
|
||||||
pub async fn accounts(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
|
pub async fn accounts(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
|
||||||
@ -45,7 +45,7 @@ pub async fn update_account(
|
|||||||
routes::admin::Error::DifferentPasswords,
|
routes::admin::Error::DifferentPasswords,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let hash = match encrypt_password(&p1, &config.lock().web().pass_salt()) {
|
let hash = match p1.encrypt(&config.lock().web().pass_salt()) {
|
||||||
Ok(hash) => hash,
|
Ok(hash) => hash,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
@ -97,7 +97,7 @@ pub async fn create_account(
|
|||||||
routes::admin::Error::DifferentPasswords,
|
routes::admin::Error::DifferentPasswords,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let hash = match encrypt_password(&payload.password, &config.lock().web().pass_salt()) {
|
let hash = match payload.password.encrypt(&config.lock().web().pass_salt()) {
|
||||||
Ok(hash) => hash,
|
Ok(hash) => hash,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
|
@ -64,7 +64,7 @@ async fn upload_product_image(
|
|||||||
let write = async {
|
let write = async {
|
||||||
let fs_manager::WriteResult {
|
let fs_manager::WriteResult {
|
||||||
local_path,
|
local_path,
|
||||||
file_name,
|
unique_name: file_name,
|
||||||
} = match fs.send(msg).await {
|
} = match fs.send(msg).await {
|
||||||
Ok(Ok(file_name)) => file_name,
|
Ok(Ok(file_name)) => file_name,
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_web::web::{Data, Json, ServiceConfig};
|
use actix_web::web::{Data, Json, ServiceConfig};
|
||||||
use actix_web::{get, post, HttpResponse};
|
use actix_web::{get, post, HttpResponse};
|
||||||
|
use config::SharedAppConfig;
|
||||||
use database_manager::{query_db, Database};
|
use database_manager::{query_db, Database};
|
||||||
use model::{Audience, FullAccount, Token, TokenString};
|
use model::{Audience, Encrypt, FullAccount, Token, TokenString};
|
||||||
use payment_manager::{PaymentManager, PaymentNotification};
|
use payment_manager::{PaymentManager, PaymentNotification};
|
||||||
use token_manager::TokenManager;
|
use token_manager::TokenManager;
|
||||||
|
|
||||||
use crate::logic::validate_password;
|
|
||||||
use crate::routes::public::Error as PublicError;
|
use crate::routes::public::Error as PublicError;
|
||||||
use crate::routes::{self, Result};
|
use crate::routes::{self, Result};
|
||||||
use crate::{public_send_db, Login, Password};
|
use crate::{public_send_db, Login, Password};
|
||||||
@ -21,6 +21,44 @@ async fn stocks(db: Data<Addr<Database>>) -> Result<HttpResponse> {
|
|||||||
public_send_db!(db.into_inner(), database_manager::AllStocks)
|
public_send_db!(db.into_inner(), database_manager::AllStocks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct CreateAccountInput {
|
||||||
|
pub email: model::Email,
|
||||||
|
pub login: Login,
|
||||||
|
pub password: Password,
|
||||||
|
pub password_confirmation: model::PasswordConfirmation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/register")]
|
||||||
|
pub async fn create_account(
|
||||||
|
db: Data<Addr<Database>>,
|
||||||
|
Json(payload): Json<CreateAccountInput>,
|
||||||
|
config: Data<SharedAppConfig>,
|
||||||
|
) -> routes::Result<HttpResponse> {
|
||||||
|
if payload.password != payload.password_confirmation {
|
||||||
|
return Err(routes::Error::Admin(
|
||||||
|
routes::admin::Error::DifferentPasswords,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let hash = match payload.password.encrypt(&config.lock().web().pass_salt()) {
|
||||||
|
Ok(hash) => hash,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
return Err(routes::Error::Admin(routes::admin::Error::HashPass));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public_send_db!(
|
||||||
|
db,
|
||||||
|
database_manager::CreateAccount {
|
||||||
|
email: payload.email,
|
||||||
|
login: payload.login,
|
||||||
|
pass_hash: model::PassHash::from(hash),
|
||||||
|
role: model::Role::User,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
pub struct SignInInput {
|
pub struct SignInInput {
|
||||||
pub login: String,
|
pub login: String,
|
||||||
@ -50,7 +88,10 @@ async fn sign_in(
|
|||||||
routes::Error::Public(PublicError::DatabaseConnection),
|
routes::Error::Public(PublicError::DatabaseConnection),
|
||||||
routes::Error::Public(PublicError::DatabaseConnection)
|
routes::Error::Public(PublicError::DatabaseConnection)
|
||||||
);
|
);
|
||||||
if validate_password(&Password::from(payload.password), &account.pass_hash).is_err() {
|
if Password::from(payload.password)
|
||||||
|
.validate(&account.pass_hash)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
return Err(routes::Error::Unauthorized);
|
return Err(routes::Error::Unauthorized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 101 KiB |
BIN
assets/examples/images/pexels-alex-azabache-3907507.webp
Normal file
After Width: | Height: | Size: 523 KiB |
BIN
assets/examples/images/pexels-binoid-cbd-3612182.webp
Normal file
After Width: | Height: | Size: 331 KiB |
BIN
assets/examples/images/pexels-caio-1279107.webp
Normal file
After Width: | Height: | Size: 1.8 MiB |
BIN
assets/examples/images/pexels-eprism-studio-335257.webp
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
assets/examples/images/pexels-gabriel-freytez-341523.webp
Normal file
After Width: | Height: | Size: 151 KiB |
BIN
assets/examples/images/pexels-jess-bailey-designs-913135.webp
Normal file
After Width: | Height: | Size: 136 KiB |
BIN
assets/examples/images/pexels-luis-quintero-1738641.webp
Normal file
After Width: | Height: | Size: 2.1 MiB |
BIN
assets/examples/images/pexels-math-90946.webp
Normal file
After Width: | Height: | Size: 328 KiB |
BIN
assets/examples/images/pexels-mike-380954.webp
Normal file
After Width: | Height: | Size: 373 KiB |
BIN
assets/examples/images/pexels-pixabay-279906.webp
Normal file
After Width: | Height: | Size: 163 KiB |
@ -7,11 +7,14 @@ edition = "2021"
|
|||||||
model = { path = "../shared/model", version = "0.1", features = ["db", "dummy"] }
|
model = { path = "../shared/model", version = "0.1", features = ["db", "dummy"] }
|
||||||
config = { path = "../shared/config" }
|
config = { path = "../shared/config" }
|
||||||
database_manager = { path = "../actors/database_manager", features = ["dummy"] }
|
database_manager = { path = "../actors/database_manager", features = ["dummy"] }
|
||||||
|
fs_manager = { path = "../actors/fs_manager", features = [] }
|
||||||
|
|
||||||
actix = { version = "0.13", features = [] }
|
actix = { version = "0.13", features = [] }
|
||||||
actix-rt = { version = "2.7", features = [] }
|
actix-rt = { version = "2.7", features = [] }
|
||||||
actix-web = { version = "4.0", features = [] }
|
actix-web = { version = "4.0", features = [] }
|
||||||
|
|
||||||
|
tokio = { version = "1.18.1", features = ["full"] }
|
||||||
|
|
||||||
fake = { version = "2.4.3", features = ["derive", "chrono", "http"] }
|
fake = { version = "2.4.3", features = ["derive", "chrono", "http"] }
|
||||||
rand = { version = "0.8.5" }
|
rand = { version = "0.8.5" }
|
||||||
|
|
||||||
@ -19,3 +22,9 @@ dotenv = { version = "0.15", features = [] }
|
|||||||
|
|
||||||
log = { version = "0.4", features = [] }
|
log = { version = "0.4", features = [] }
|
||||||
pretty_env_logger = { version = "0.4", features = [] }
|
pretty_env_logger = { version = "0.4", features = [] }
|
||||||
|
|
||||||
|
password-hash = { version = "0.4", features = ["alloc"] }
|
||||||
|
|
||||||
|
thiserror = { version = "1.0.31" }
|
||||||
|
|
||||||
|
human-panic = { version = "1.0.3" }
|
||||||
|
37
db-seed/src/accounts.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use actix::Addr;
|
||||||
|
use config::SharedAppConfig;
|
||||||
|
use database_manager::{query_db, Database};
|
||||||
|
use fake::{Fake, Faker};
|
||||||
|
|
||||||
|
use crate::{Result, SharedState};
|
||||||
|
|
||||||
|
pub(crate) async fn create_accounts(
|
||||||
|
db: Addr<Database>,
|
||||||
|
seed: SharedState,
|
||||||
|
_config: SharedAppConfig,
|
||||||
|
) -> Result<()> {
|
||||||
|
let accounts: Vec<model::FullAccount> =
|
||||||
|
query_db!(db, database_manager::AllAccounts, default vec![]);
|
||||||
|
if accounts.len() >= 10 {
|
||||||
|
seed.lock().unwrap().accounts = accounts;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut accounts = Vec::with_capacity(10);
|
||||||
|
for _ in 0..10 {
|
||||||
|
let msg: database_manager::CreateAccount = Faker.fake::<database_manager::CreateAccount>();
|
||||||
|
|
||||||
|
match db.send(msg).await {
|
||||||
|
Ok(Ok(account)) => accounts.push(account),
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("{e}")
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seed.lock().unwrap().accounts = accounts;
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -1,37 +1,63 @@
|
|||||||
|
mod accounts;
|
||||||
|
mod photos;
|
||||||
|
mod product_photos;
|
||||||
|
mod products;
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use actix::Actor;
|
use actix::Actor;
|
||||||
use config::{AppConfig, UpdateConfig};
|
use config::{AppConfig, UpdateConfig};
|
||||||
use fake::{Fake, Faker};
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("Photo with name {0:?} was not found")]
|
||||||
|
PhotoNotFound(String),
|
||||||
|
#[error("Failed to create product record in database")]
|
||||||
|
DbProduct,
|
||||||
|
#[error("Failed to attach photo {1:?} to product {0:?} in database")]
|
||||||
|
DbProductPhoto(model::ProductId, model::PhotoId),
|
||||||
|
#[error("Product with name {0:?} does not exists")]
|
||||||
|
NoProduct(String),
|
||||||
|
#[error("Failed to create photo record for file {0:?}")]
|
||||||
|
WritePhoto(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
pub struct Opts;
|
pub struct Opts;
|
||||||
impl UpdateConfig for Opts {
|
impl UpdateConfig for Opts {
|
||||||
fn update_config(&self, _config: &mut AppConfig) {}
|
fn update_config(&self, _config: &mut AppConfig) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct DbSeed {
|
||||||
|
pub accounts: Vec<model::FullAccount>,
|
||||||
|
pub products: Vec<model::Product>,
|
||||||
|
pub photos: Vec<model::Photo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) type SharedState = Arc<Mutex<DbSeed>>;
|
||||||
|
|
||||||
#[actix_web::main]
|
#[actix_web::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
human_panic::setup_panic!();
|
||||||
|
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
std::env::set_var("RUST_LOG", "DEBUG");
|
std::env::set_var("RUST_LOG", "DEBUG");
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
|
let db_seed = Arc::new(Mutex::new(DbSeed::default()));
|
||||||
let config = config::default_load(&Opts);
|
let config = config::default_load(&Opts);
|
||||||
let db = database_manager::Database::build(config)
|
|
||||||
|
let db = database_manager::Database::build(config.clone())
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.start();
|
.start();
|
||||||
|
|
||||||
let mut users = Vec::with_capacity(10);
|
let res = tokio::join!(
|
||||||
for _ in 0..10 {
|
accounts::create_accounts(db.clone(), db_seed.clone(), config.clone()),
|
||||||
match db
|
products::create_products(db.clone(), db_seed.clone(), config.clone())
|
||||||
.send(Faker.fake::<database_manager::CreateAccount>())
|
);
|
||||||
.await
|
res.0.unwrap();
|
||||||
{
|
res.1.unwrap();
|
||||||
Ok(Ok(user)) => users.push(user),
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
log::error!("{e}")
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("{e}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
152
db-seed/src/photos.rs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
use actix::{Actor, Addr};
|
||||||
|
use config::SharedAppConfig;
|
||||||
|
use database_manager::{query_db, Database};
|
||||||
|
use fs_manager::query_fs;
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
|
use crate::{Result, SharedState};
|
||||||
|
|
||||||
|
async fn create_photo(
|
||||||
|
db: Addr<Database>,
|
||||||
|
seed: SharedState,
|
||||||
|
fs: Addr<fs_manager::FsManager>,
|
||||||
|
file: &'static str,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
let read = async move {
|
||||||
|
let mut file =
|
||||||
|
tokio::fs::File::open(std::path::Path::new("./assets/examples/images").join(file))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
while let Ok(b) = file.read_u8().await {
|
||||||
|
tx.send(b).unwrap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let write = async {
|
||||||
|
let fs_manager::WriteResult {
|
||||||
|
unique_name: _,
|
||||||
|
local_path,
|
||||||
|
} = query_fs!(
|
||||||
|
fs,
|
||||||
|
fs_manager::WriteFile {
|
||||||
|
file_name: file.to_string(),
|
||||||
|
stream: rx,
|
||||||
|
},
|
||||||
|
panic
|
||||||
|
);
|
||||||
|
let photo: model::Photo = query_db!(
|
||||||
|
db,
|
||||||
|
database_manager::CreatePhoto {
|
||||||
|
local_path,
|
||||||
|
file_name: model::FileName::new(file)
|
||||||
|
},
|
||||||
|
crate::Error::WritePhoto(file.into())
|
||||||
|
);
|
||||||
|
seed.lock().unwrap().photos.push(photo);
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
let (_, res) = tokio::join!(read, write);
|
||||||
|
log::debug!("Photo {:?} done", file);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn create_photos(
|
||||||
|
db: Addr<Database>,
|
||||||
|
seed: SharedState,
|
||||||
|
config: SharedAppConfig,
|
||||||
|
) -> Result<()> {
|
||||||
|
log::debug!("Creating photos");
|
||||||
|
let photos: Vec<model::Photo> = query_db!(db, database_manager::AllPhotos, default vec![]);
|
||||||
|
if photos.len() >= 10 {
|
||||||
|
seed.lock().unwrap().photos = photos;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let fs = fs_manager::FsManager::build(config.clone())
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
let results = tokio::join!(
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-alex-azabache-3907507.webp"
|
||||||
|
),
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-binoid-cbd-3612182.webp"
|
||||||
|
),
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-caio-1279107.webp"
|
||||||
|
),
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-eprism-studio-335257.webp"
|
||||||
|
),
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-gabriel-freytez-341523.webp"
|
||||||
|
),
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-jess-bailey-designs-913135.webp"
|
||||||
|
),
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-luis-quintero-1738641.webp"
|
||||||
|
),
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-math-90946.webp"
|
||||||
|
),
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-mike-380954.webp"
|
||||||
|
),
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-pixabay-279906.webp"
|
||||||
|
),
|
||||||
|
create_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
fs.clone(),
|
||||||
|
"pexels-Venus-HD-Make-up-and-perfume-2587370.webp"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
results.0.unwrap();
|
||||||
|
results.1.unwrap();
|
||||||
|
results.2.unwrap();
|
||||||
|
results.3.unwrap();
|
||||||
|
results.4.unwrap();
|
||||||
|
results.5.unwrap();
|
||||||
|
results.6.unwrap();
|
||||||
|
results.7.unwrap();
|
||||||
|
results.8.unwrap();
|
||||||
|
results.9.unwrap();
|
||||||
|
results.10.unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
139
db-seed/src/product_photos.rs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
use actix::Addr;
|
||||||
|
use config::SharedAppConfig;
|
||||||
|
use database_manager::{query_db, Database};
|
||||||
|
|
||||||
|
use crate::{Result, SharedState};
|
||||||
|
|
||||||
|
async fn create_product_photo(
|
||||||
|
db: Addr<Database>,
|
||||||
|
seed: SharedState,
|
||||||
|
name: &'static str,
|
||||||
|
file_name: &'static str,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let product_id = {
|
||||||
|
seed.lock()
|
||||||
|
.unwrap()
|
||||||
|
.products
|
||||||
|
.iter()
|
||||||
|
.find(|p| {
|
||||||
|
let _x = 1;
|
||||||
|
p.name.as_str() == name
|
||||||
|
})
|
||||||
|
.map(|p| p.id)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
let _x = 1;
|
||||||
|
crate::Error::NoProduct(name.into())
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
let photo_id = {
|
||||||
|
seed.lock()
|
||||||
|
.unwrap()
|
||||||
|
.photos
|
||||||
|
.iter()
|
||||||
|
.find(|p| p.file_name.as_str() == file_name)
|
||||||
|
.map(|p| p.id)
|
||||||
|
.ok_or_else(|| crate::Error::PhotoNotFound(file_name.into()))?
|
||||||
|
};
|
||||||
|
|
||||||
|
query_db!(
|
||||||
|
db,
|
||||||
|
database_manager::CreateProductPhoto {
|
||||||
|
product_id,
|
||||||
|
photo_id,
|
||||||
|
},
|
||||||
|
crate::Error::DbProductPhoto(product_id, photo_id)
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn create_product_photos(
|
||||||
|
db: Addr<Database>,
|
||||||
|
seed: SharedState,
|
||||||
|
_config: SharedAppConfig,
|
||||||
|
) -> Result<()> {
|
||||||
|
let product_photos = query_db!(db, database_manager::AllProductPhotos, default vec![]);
|
||||||
|
if product_photos.len() >= 10 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let results = tokio::join!(
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Nikon",
|
||||||
|
"pexels-alex-azabache-3907507.webp"
|
||||||
|
),
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Bonoid CBD",
|
||||||
|
"pexels-binoid-cbd-3612182.webp"
|
||||||
|
),
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Casio Speaker",
|
||||||
|
"pexels-caio-1279107.webp"
|
||||||
|
),
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Eprism Studio",
|
||||||
|
"pexels-eprism-studio-335257.webp"
|
||||||
|
),
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Best Phones 2022",
|
||||||
|
"pexels-gabriel-freytez-341523.webp"
|
||||||
|
),
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Sweet cake",
|
||||||
|
"pexels-jess-bailey-designs-913135.webp"
|
||||||
|
),
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Lexal 128G",
|
||||||
|
"pexels-luis-quintero-1738641.webp"
|
||||||
|
),
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Fujifilm X-T10",
|
||||||
|
"pexels-math-90946.webp"
|
||||||
|
),
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Sweet Tower",
|
||||||
|
"pexels-mike-380954.webp"
|
||||||
|
),
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Nikon Lenses",
|
||||||
|
"pexels-pixabay-279906.webp"
|
||||||
|
),
|
||||||
|
create_product_photo(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Venus HD Professional",
|
||||||
|
"pexels-Venus-HD-Make-up-and-perfume-2587370.webp"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
results.0.unwrap();
|
||||||
|
results.1.unwrap();
|
||||||
|
results.2.unwrap();
|
||||||
|
results.3.unwrap();
|
||||||
|
results.4.unwrap();
|
||||||
|
results.5.unwrap();
|
||||||
|
results.6.unwrap();
|
||||||
|
results.7.unwrap();
|
||||||
|
results.8.unwrap();
|
||||||
|
results.9.unwrap();
|
||||||
|
results.10.unwrap();
|
||||||
|
Ok(())
|
||||||
|
}
|
87
db-seed/src/products.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use actix::Addr;
|
||||||
|
use config::SharedAppConfig;
|
||||||
|
use database_manager::{query_db, Database};
|
||||||
|
use fake::{Fake, Faker};
|
||||||
|
use model::{ProductCategory, ProductName};
|
||||||
|
|
||||||
|
use crate::product_photos::create_product_photos;
|
||||||
|
use crate::{Result, SharedState};
|
||||||
|
|
||||||
|
async fn create_product(
|
||||||
|
db: Addr<Database>,
|
||||||
|
seed: SharedState,
|
||||||
|
name: &'static str,
|
||||||
|
category: &'static str,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let seed = seed.clone();
|
||||||
|
let db = db.clone();
|
||||||
|
let product = query_db!(
|
||||||
|
db,
|
||||||
|
database_manager::CreateProduct {
|
||||||
|
name: ProductName::from(String::from(name)),
|
||||||
|
short_description: Faker.fake(),
|
||||||
|
long_description: Faker.fake(),
|
||||||
|
category: Some(ProductCategory::new(category)),
|
||||||
|
price: Faker.fake(),
|
||||||
|
deliver_days_flag: Faker.fake(),
|
||||||
|
},
|
||||||
|
crate::Error::DbProduct
|
||||||
|
);
|
||||||
|
|
||||||
|
seed.lock().unwrap().products.push(product);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn create_products(
|
||||||
|
db: Addr<Database>,
|
||||||
|
seed: SharedState,
|
||||||
|
config: SharedAppConfig,
|
||||||
|
) -> Result<()> {
|
||||||
|
crate::photos::create_photos(db.clone(), seed.clone(), config.clone())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let products = query_db!(db, database_manager::AllProducts, default vec![]);
|
||||||
|
if products.len() >= 10 {
|
||||||
|
{
|
||||||
|
seed.lock().unwrap().products = products;
|
||||||
|
}
|
||||||
|
if let Err(e) = create_product_photos(db.clone(), seed.clone(), config.clone()).await {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let results = tokio::join!(
|
||||||
|
create_product(db.clone(), seed.clone(), "Nikon", "Cameras",),
|
||||||
|
create_product(db.clone(), seed.clone(), "Bonoid CBD", "Drugstore",),
|
||||||
|
create_product(db.clone(), seed.clone(), "Casio Speaker", "Speakers",),
|
||||||
|
create_product(db.clone(), seed.clone(), "Eprism Studio", "Drugstore",),
|
||||||
|
create_product(db.clone(), seed.clone(), "Best Phones 2022", "Phones",),
|
||||||
|
create_product(db.clone(), seed.clone(), "Sweet cake", "Sweets",),
|
||||||
|
create_product(db.clone(), seed.clone(), "Lexal 128G", "Memory",),
|
||||||
|
create_product(db.clone(), seed.clone(), "Fujifilm X-T10", "Cameras",),
|
||||||
|
create_product(db.clone(), seed.clone(), "Sweet Tower", "Sweets",),
|
||||||
|
create_product(db.clone(), seed.clone(), "Nikon Lenses", "Cameras",),
|
||||||
|
create_product(
|
||||||
|
db.clone(),
|
||||||
|
seed.clone(),
|
||||||
|
"Venus HD Professional",
|
||||||
|
"Drugstore",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
results.0.unwrap();
|
||||||
|
results.1.unwrap();
|
||||||
|
results.2.unwrap();
|
||||||
|
results.3.unwrap();
|
||||||
|
results.4.unwrap();
|
||||||
|
results.5.unwrap();
|
||||||
|
results.6.unwrap();
|
||||||
|
results.7.unwrap();
|
||||||
|
results.8.unwrap();
|
||||||
|
results.9.unwrap();
|
||||||
|
results.10.unwrap();
|
||||||
|
|
||||||
|
create_product_photos(db.clone(), seed.clone(), config.clone()).await
|
||||||
|
}
|
@ -22,5 +22,11 @@ thiserror = { version = "1.0.31" }
|
|||||||
|
|
||||||
validator = { version = "0.15.0" }
|
validator = { version = "0.15.0" }
|
||||||
|
|
||||||
fake = { version = "2.4.3", features = ["derive", "chrono", "http", "uuid"], optional = true }
|
fake = { version = "2.4.3", features = ["derive", "chrono", "http", "uuid", "dummy"], optional = true }
|
||||||
rand = { version = "0.8.5", optional = true }
|
rand = { version = "0.8.5", optional = true }
|
||||||
|
|
||||||
|
password-hash = { version = "0.4", features = ["alloc"] }
|
||||||
|
argon2 = { version = "0.4", features = ["parallel", "password-hash"] }
|
||||||
|
rand_core = { version = "0.6", features = ["std"] }
|
||||||
|
|
||||||
|
log = { version = "0.4.17" }
|
||||||
|
78
shared/model/src/dummy.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
use fake::faker::internet::en::{FreeEmail, Password as FakePass, Username};
|
||||||
|
use fake::Fake;
|
||||||
|
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
impl<T> fake::Dummy<T> for Login {
|
||||||
|
fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
|
||||||
|
Self(Username().fake())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fake::Dummy<T> for Email {
|
||||||
|
fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
|
||||||
|
Self(FreeEmail().fake())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fake::Dummy<T> for ProductShortDesc {
|
||||||
|
fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
|
||||||
|
use fake::faker::lorem::en::Words;
|
||||||
|
let words: Vec<String> = Words(5..20).fake();
|
||||||
|
Self(words.join(" "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fake::Dummy<T> for ProductLongDesc {
|
||||||
|
fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
|
||||||
|
use fake::faker::lorem::en::Paragraphs;
|
||||||
|
let words: Vec<String> = Paragraphs(10..20).fake();
|
||||||
|
Self(words.join("\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fake::Dummy<T> for Password {
|
||||||
|
fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
|
||||||
|
let pass: String = FakePass(6..20).fake();
|
||||||
|
Self(pass)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fake::Dummy<T> for PassHash {
|
||||||
|
fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
|
||||||
|
let pass: Password = fake::Faker.fake();
|
||||||
|
Self(
|
||||||
|
pass.encrypt(
|
||||||
|
&password_hash::SaltString::new("a7sydd98asd98ahsda9shdahd98ahsd9aysd9aysd9y")
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fake::Dummy<T> for ProductName {
|
||||||
|
fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
|
||||||
|
use fake::faker::lorem::en::Words;
|
||||||
|
let name: Vec<String> = Words(5..8).fake();
|
||||||
|
Self(name.join(" "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fake::Dummy<T> for Price {
|
||||||
|
fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
|
||||||
|
let price = rand::random::<u8>() as i32 * 100i32 + rand::random::<u8>() as i32;
|
||||||
|
|
||||||
|
let price: i32 = price as i32;
|
||||||
|
Self(NonNegative(price))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> fake::Dummy<T> for NonNegative {
|
||||||
|
fn dummy_with_rng<R: Rng + ?Sized>(_config: &T, _rng: &mut R) -> Self {
|
||||||
|
let price = rand::random::<u8>() as i32 * 100i32 + rand::random::<u8>() as i32;
|
||||||
|
|
||||||
|
let price: i32 = price as i32;
|
||||||
|
Self(price)
|
||||||
|
}
|
||||||
|
}
|
34
shared/model/src/encrypt.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use argon2::{Algorithm, Argon2, Params, Version};
|
||||||
|
use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
|
||||||
|
|
||||||
|
pub trait Encrypt {
|
||||||
|
fn encrypt(&self, salt: &SaltString) -> password_hash::Result<String>;
|
||||||
|
|
||||||
|
fn validate(&self, pass_hash: &crate::PassHash) -> password_hash::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encrypt for crate::Password {
|
||||||
|
fn encrypt(&self, salt: &SaltString) -> password_hash::Result<String> {
|
||||||
|
log::debug!("Hashing password {:?}", self);
|
||||||
|
Ok(
|
||||||
|
Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default())
|
||||||
|
.hash_password(self.as_bytes(), &salt)?
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate(&self, pass_hash: &crate::PassHash) -> password_hash::Result<()> {
|
||||||
|
log::debug!("Validating password {:?} {:?}", self, pass_hash);
|
||||||
|
|
||||||
|
Argon2::default().verify_password(
|
||||||
|
self.as_bytes(),
|
||||||
|
&PasswordHash::new(pass_hash.as_str()).expect("Invalid hashed password"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_hash() {
|
||||||
|
use argon2::password_hash::rand_core::OsRng;
|
||||||
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
println!("{salt}");
|
||||||
|
}
|
@ -2,15 +2,22 @@
|
|||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
|
|
||||||
|
#[cfg(feature = "dummy")]
|
||||||
|
mod dummy;
|
||||||
|
pub mod encrypt;
|
||||||
|
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use derive_more::{Deref, Display, From};
|
use derive_more::{Deref, Display, From};
|
||||||
#[cfg(feature = "dummy")]
|
#[cfg(feature = "dummy")]
|
||||||
use fake::Fake;
|
use fake::Fake;
|
||||||
|
use rand::Rng;
|
||||||
use serde::de::{Error, Visitor};
|
use serde::de::{Error, Visitor};
|
||||||
use serde::{Deserialize, Deserializer, Serialize};
|
use serde::{Deserialize, Deserializer, Serialize};
|
||||||
|
|
||||||
|
pub use crate::encrypt::*;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum TransformError {
|
pub enum TransformError {
|
||||||
#[error("Given value is below minimal value")]
|
#[error("Given value is below minimal value")]
|
||||||
@ -151,7 +158,6 @@ impl Default for Audience {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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(Serialize, Deserialize, Default, Debug, Copy, Clone, Deref, From)]
|
#[derive(Serialize, Deserialize, Default, Debug, Copy, Clone, Deref, From)]
|
||||||
@ -173,16 +179,12 @@ impl TryFrom<i32> for Quantity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
|
||||||
#[cfg_attr(feature = "dummy", dummy("fake::faker::internet::en::Username"))]
|
|
||||||
#[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(Deserialize, Serialize, Debug, Deref, From, Display)]
|
#[derive(Deserialize, Serialize, Debug, Deref, From, Display)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct Login(String);
|
pub struct Login(String);
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
|
||||||
#[cfg_attr(feature = "dummy", dummy("fake::faker::internet::en::FreeEmail"))]
|
|
||||||
#[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(Serialize, Debug, Deref, From, Display)]
|
#[derive(Serialize, Debug, Deref, From, Display)]
|
||||||
@ -230,7 +232,6 @@ impl<'de> serde::Deserialize<'de> for Email {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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(Serialize, Default, Debug, Copy, Clone, Deref, Display)]
|
#[derive(Serialize, Default, Debug, Copy, Clone, Deref, Display)]
|
||||||
@ -421,13 +422,18 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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(Serialize, Deserialize, Debug, Deref, From, Display)]
|
#[derive(Serialize, Deserialize, Debug, Deref, From, Display)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct Password(String);
|
pub struct Password(String);
|
||||||
|
|
||||||
|
impl Password {
|
||||||
|
pub fn new<S: Into<String>>(pass: S) -> Self {
|
||||||
|
Self(pass.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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))]
|
||||||
@ -435,7 +441,6 @@ pub struct Password(String);
|
|||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct PasswordConfirmation(String);
|
pub struct PasswordConfirmation(String);
|
||||||
|
|
||||||
#[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(Serialize, Deserialize, Debug, Deref, From, Display)]
|
#[derive(Serialize, Deserialize, Debug, Deref, From, Display)]
|
||||||
@ -457,7 +462,7 @@ pub struct AccountId(RecordId);
|
|||||||
|
|
||||||
#[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(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct FullAccount {
|
pub struct FullAccount {
|
||||||
pub id: AccountId,
|
pub id: AccountId,
|
||||||
pub email: Email,
|
pub email: Email,
|
||||||
@ -510,21 +515,18 @@ impl From<FullAccount> for Account {
|
|||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ProductId(RecordId);
|
pub struct ProductId(RecordId);
|
||||||
|
|
||||||
#[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(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ProductName(String);
|
pub struct ProductName(String);
|
||||||
|
|
||||||
#[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(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ProductShortDesc(String);
|
pub struct ProductShortDesc(String);
|
||||||
|
|
||||||
#[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(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
|
||||||
@ -538,9 +540,15 @@ pub struct ProductLongDesc(String);
|
|||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ProductCategory(String);
|
pub struct ProductCategory(String);
|
||||||
|
|
||||||
|
impl ProductCategory {
|
||||||
|
pub fn new<S: Into<String>>(s: S) -> Self {
|
||||||
|
Self(s.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Product {
|
pub struct Product {
|
||||||
pub id: ProductId,
|
pub id: ProductId,
|
||||||
pub name: ProductName,
|
pub name: ProductName,
|
||||||
@ -732,10 +740,16 @@ pub struct LocalPath(String);
|
|||||||
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
|
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
|
||||||
pub struct FileName(String);
|
pub struct FileName(String);
|
||||||
|
|
||||||
|
impl FileName {
|
||||||
|
pub fn new<S: Into<String>>(s: S) -> Self {
|
||||||
|
Self(s.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[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(Serialize, Deserialize, Debug, Deref, Display, From)]
|
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Deref, Display, From)]
|
||||||
pub struct PhotoId(RecordId);
|
pub struct PhotoId(RecordId);
|
||||||
|
|
||||||
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
|