Add seed and photos

This commit is contained in:
Adrian Woźniak 2022-05-06 16:02:38 +02:00
parent 1b75a6a1ac
commit ff86e9929c
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
27 changed files with 466 additions and 799 deletions

3
.env
View File

@ -14,3 +14,6 @@ PAYU_CLIENT_SECRET="12f071174cb7eb79d4aac5bc2f07563f"
PAYU_CLIENT_MERCHANT_ID=300746 PAYU_CLIENT_MERCHANT_ID=300746
WEB_HOST=https://bazzar.ita-prog.pl WEB_HOST=https://bazzar.ita-prog.pl
FILES_PUBLIC_PATH=/files
FILES_LOCAL_PATH=./tmp

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
/target /target
bazzar.toml bazzar.toml
/tmp
/uploads

101
Cargo.lock generated
View File

@ -616,6 +616,7 @@ dependencies = [
"derive_more", "derive_more",
"dotenv", "dotenv",
"email_manager", "email_manager",
"fs_manager",
"futures", "futures",
"futures-util", "futures-util",
"gumdrop", "gumdrop",
@ -1034,6 +1035,41 @@ dependencies = [
"cipher", "cipher",
] ]
[[package]]
name = "darling"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]] [[package]]
name = "dashmap" name = "dashmap"
version = "4.0.2" version = "4.0.2"
@ -1058,15 +1094,34 @@ dependencies = [
"actix-rt", "actix-rt",
"chrono", "chrono",
"config", "config",
"fake",
"log", "log",
"model", "model",
"pretty_env_logger", "pretty_env_logger",
"rand",
"sqlx", "sqlx",
"sqlx-core", "sqlx-core",
"thiserror", "thiserror",
"uuid", "uuid",
] ]
[[package]]
name = "db-seed"
version = "0.1.0"
dependencies = [
"actix 0.13.0",
"actix-rt",
"actix-web",
"config",
"database_manager",
"dotenv",
"fake",
"log",
"model",
"pretty_env_logger",
"rand",
]
[[package]] [[package]]
name = "dbg" name = "dbg"
version = "1.0.4" version = "1.0.4"
@ -1147,6 +1202,17 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dummy"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bf5ff6f74150b0bbb6e6057718a903b3d8f3fc7096c9190fc162ca99d3b2273"
dependencies = [
"darling",
"quote",
"syn",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@ -1212,6 +1278,19 @@ version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
[[package]]
name = "fake"
version = "2.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21a8531dd3a64fd1cfbe92fad4160bc2060489c6195fe847e045e5788f710bae"
dependencies = [
"chrono",
"dummy",
"http",
"rand",
"uuid",
]
[[package]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -1737,6 +1816,12 @@ dependencies = [
"tokio-native-tls", "tokio-native-tls",
] ]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.2.3" version = "0.2.3"
@ -1987,6 +2072,12 @@ dependencies = [
"digest 0.10.3", "digest 0.10.3",
] ]
[[package]]
name = "md5"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.4.1" version = "2.4.1"
@ -2095,6 +2186,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"derive_more", "derive_more",
"fake",
"rand",
"serde", "serde",
"sqlx", "sqlx",
"sqlx-core", "sqlx-core",
@ -3376,6 +3469,12 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]] [[package]]
name = "subtle" name = "subtle"
version = "2.4.1" version = "2.4.1"
@ -3922,7 +4021,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"md5",
"serde", "serde",
"sha1",
] ]
[[package]] [[package]]

View File

@ -11,4 +11,5 @@ members = [
"actors/search_manager", "actors/search_manager",
"actors/token_manager", "actors/token_manager",
"actors/fs_manager", "actors/fs_manager",
"db-seed"
] ]

View File

@ -3,6 +3,9 @@ name = "database_manager"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[features]
dummy = ["fake", "rand"]
[dependencies] [dependencies]
model = { path = "../../shared/model" } model = { path = "../../shared/model" }
config = { path = "../../shared/config" } config = { path = "../../shared/config" }
@ -20,3 +23,6 @@ chrono = { version = "0.4", features = ["serde"] }
log = { version = "0.4", features = [] } log = { version = "0.4", features = [] }
pretty_env_logger = { version = "0.4", features = [] } pretty_env_logger = { version = "0.4", features = [] }
fake = { version = "2.4.3", features = ["derive", "chrono", "http", "uuid"], optional = true }
rand = { version = "0.8.5", optional = true }

View File

@ -3,7 +3,7 @@ use sqlx::PgPool;
use super::Result; use super::Result;
use crate::{ use crate::{
create_order_item, db_async_handler, shopping_cart_set_state, CreateOrderItem, Database, create_order_item, db_async_handler, shopping_cart_set_state, CreateOrderItem,
ShoppingCartSetState, ShoppingCartSetState,
}; };

View File

@ -1,8 +1,10 @@
#[cfg(feature = "dummy")]
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 super::Result;
use crate::{db_async_handler, Database}; use crate::db_async_handler;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
@ -37,10 +39,13 @@ FROM accounts
}) })
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(actix::Message)] #[derive(actix::Message)]
#[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,
@ -68,6 +73,7 @@ RETURNING id, email, login, pass_hash, role, customer_id, state
}) })
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<FullAccount>")] #[rtype(result = "Result<FullAccount>")]
pub struct UpdateAccount { pub struct UpdateAccount {

View File

@ -1,18 +1,23 @@
pub use account_orders::*;
pub use accounts::*;
use actix::{Actor, Context}; use actix::{Actor, Context};
use config::SharedAppConfig; use config::SharedAppConfig;
pub use order_items::*;
pub use products::*;
pub use shopping_cart_items::*;
pub use shopping_carts::*;
use sqlx::PgPool; use sqlx::PgPool;
pub use stocks::*;
pub use tokens::*; pub use crate::account_orders::*;
pub use crate::accounts::*;
pub use crate::order_items::*;
pub use crate::photos::*;
pub use crate::product_photos::*;
pub use crate::products::*;
pub use crate::shopping_cart_items::*;
pub use crate::shopping_carts::*;
pub use crate::stocks::*;
pub use crate::tokens::*;
pub mod account_orders; pub mod account_orders;
pub mod accounts; pub mod accounts;
pub mod order_items; pub mod order_items;
pub mod photos;
pub mod product_photos;
pub mod products; pub mod products;
pub mod shopping_cart_items; pub mod shopping_cart_items;
pub mod shopping_carts; pub mod shopping_carts;
@ -22,7 +27,7 @@ pub mod tokens;
#[macro_export] #[macro_export]
macro_rules! db_async_handler { macro_rules! db_async_handler {
($msg: ty, $async: ident, $res: ty) => { ($msg: ty, $async: ident, $res: ty) => {
impl actix::Handler<$msg> for Database { impl actix::Handler<$msg> for crate::Database {
type Result = actix::ResponseActFuture<Self, Result<$res>>; type Result = actix::ResponseActFuture<Self, Result<$res>>;
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
@ -47,7 +52,7 @@ macro_rules! db_async_handler {
} }
} }
impl actix::Handler<$msg> for Database { impl actix::Handler<$msg> for crate::Database {
type Result = actix::ResponseActFuture<Self, Result<$res>>; type Result = actix::ResponseActFuture<Self, Result<$res>>;
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
@ -114,6 +119,10 @@ pub enum Error {
ShoppingCartItem(#[from] shopping_cart_items::Error), ShoppingCartItem(#[from] shopping_cart_items::Error),
#[error("{0}")] #[error("{0}")]
Token(#[from] tokens::Error), Token(#[from] tokens::Error),
#[error("{0}")]
Photo(#[from] photos::Error),
#[error("{0}")]
ProductPhoto(#[from] product_photos::Error),
} }
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;

View File

@ -1,8 +1,10 @@
#[cfg(feature = "dummy")]
use fake::Fake;
use model::*; use model::*;
use sqlx::PgPool; use sqlx::PgPool;
use super::Result; use super::Result;
use crate::{db_async_handler, Database}; use crate::db_async_handler;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
@ -40,6 +42,7 @@ ORDER BY id DESC
}) })
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<OrderItem>")] #[rtype(result = "Result<OrderItem>")]
pub struct CreateOrderItem { pub struct CreateOrderItem {

View File

@ -0,0 +1,37 @@
use crate::Result;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Failed to create photo")]
Create,
}
#[derive(actix::Message)]
#[rtype(result = "Result<model::Photo>")]
pub struct CreatePhoto {
pub local_path: model::LocalPath,
pub file_name: model::FileName,
}
crate::db_async_handler!(CreatePhoto, create_photo, model::Photo, inner_create_photo);
pub(crate) async fn create_photo<'e, E>(msg: CreatePhoto, pool: E) -> Result<model::Photo>
where
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
{
sqlx::query_as(
r#"
INSERT INTO photos (file_name, local_path)
VALUES ($1, $2)
RETURNING id, local_path, file_name
"#,
)
.bind(msg.file_name)
.bind(msg.local_path)
.fetch_one(pool)
.await
.map_err(|e| {
log::error!("{e:?}");
crate::Error::Photo(Error::Create)
})
}

View File

@ -0,0 +1,2 @@
#[derive(Debug, thiserror::Error)]
pub enum Error {}

View File

@ -1,11 +1,12 @@
use actix::Message; use actix::Message;
#[cfg(feature = "dummy")]
use fake::Fake;
use model::{ use model::{
Days, Price, Product, ProductCategory, ProductId, ProductLongDesc, ProductName, Days, Price, Product, ProductCategory, ProductId, ProductLongDesc, ProductName,
ProductShortDesc, ProductShortDesc,
}; };
use super::Result; use super::Result;
use crate::Database;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {
@ -51,6 +52,7 @@ FROM products
}) })
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Message)] #[derive(Message)]
#[rtype(result = "Result<model::Product>")] #[rtype(result = "Result<model::Product>")]
pub struct CreateProduct { pub struct CreateProduct {

View File

@ -2,7 +2,7 @@ use model::*;
use sqlx::PgPool; use sqlx::PgPool;
use super::Result; use super::Result;
use crate::{db_async_handler, Database}; use crate::db_async_handler;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {

View File

@ -2,7 +2,7 @@ use model::*;
use sqlx::PgPool; use sqlx::PgPool;
use super::Result; use super::Result;
use crate::{db_async_handler, Database}; use crate::db_async_handler;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {

View File

@ -2,7 +2,7 @@ use actix::Message;
use model::{ProductId, Quantity, QuantityUnit, Stock, StockId}; use model::{ProductId, Quantity, QuantityUnit, Stock, StockId};
use sqlx::PgPool; use sqlx::PgPool;
use crate::{Database, Result}; use crate::Result;
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {

View File

@ -2,7 +2,7 @@ use actix::Message;
use model::{AccountId, Audience, Token}; use model::{AccountId, Audience, Token};
use sqlx::PgPool; use sqlx::PgPool;
use crate::{db_async_handler, Database, Result}; use crate::{db_async_handler, Result};
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub enum Error { pub enum Error {

View File

@ -4,7 +4,7 @@ use std::ffi::OsStr;
use std::io::Write; use std::io::Write;
use config::SharedAppConfig; use config::SharedAppConfig;
use model::FileName; use model::{FileName, LocalPath};
#[macro_export] #[macro_export]
macro_rules! fs_async_handler { macro_rules! fs_async_handler {
@ -37,6 +37,7 @@ pub enum Error {
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
#[derive(Clone)]
pub struct FsManager { pub struct FsManager {
config: SharedAppConfig, config: SharedAppConfig,
} }
@ -54,6 +55,9 @@ impl FsManager {
async fn validate_fs(config: SharedAppConfig) -> Result<()> { async fn validate_fs(config: SharedAppConfig) -> Result<()> {
let l = config.lock(); let l = config.lock();
let output_path = l.files().local_path(); let output_path = l.files().local_path();
tokio::fs::create_dir_all(&output_path).await.ok();
let test_file = std::path::PathBuf::new() let test_file = std::path::PathBuf::new()
.join(output_path) .join(output_path)
.join(format!("{}", uuid::Uuid::new_v4())); .join(format!("{}", uuid::Uuid::new_v4()));
@ -93,16 +97,21 @@ pub(crate) async fn remove_file(msg: RemoveFile, config: SharedAppConfig) -> Res
} }
} }
pub struct WriteResult {
pub file_name: FileName,
pub local_path: LocalPath,
}
#[derive(actix::Message)] #[derive(actix::Message)]
#[rtype(result = "Result<FileName>")] #[rtype(result = "Result<WriteResult>")]
pub struct WriteFile { pub struct WriteFile {
pub file_name: String, pub file_name: String,
pub stream: tokio::sync::mpsc::UnboundedReceiver<u8>, pub stream: tokio::sync::mpsc::UnboundedReceiver<u8>,
} }
fs_async_handler!(WriteFile, write_file, FileName); fs_async_handler!(WriteFile, write_file, WriteResult);
pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Result<FileName> { pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Result<WriteResult> {
let WriteFile { let WriteFile {
file_name, file_name,
mut stream, mut stream,
@ -128,8 +137,10 @@ pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Resul
l.files().local_path() l.files().local_path()
}; };
let path = std::path::PathBuf::new().join(output_path).join(&file_name); let path = std::path::PathBuf::new()
let mut file = match std::fs::File::create(path) { .join(&output_path)
.join(&file_name);
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)),
}; };
@ -141,5 +152,8 @@ pub(crate) async fn write_file(msg: WriteFile, config: SharedAppConfig) -> Resul
Err(e) => return Err(Error::CantWrite(e)), Err(e) => return Err(Error::CantWrite(e)),
} }
} }
Ok(FileName::from(file_name)) Ok(WriteResult {
file_name: FileName::from(file_name),
local_path: LocalPath::from(path.to_str().unwrap_or_default().to_string()),
})
} }

View File

@ -13,6 +13,7 @@ order_manager = { path = "../actors/order_manager" }
payment_manager = { path = "../actors/payment_manager" } payment_manager = { path = "../actors/payment_manager" }
search_manager = { path = "../actors/search_manager" } search_manager = { path = "../actors/search_manager" }
token_manager = { path = "../actors/token_manager" } token_manager = { path = "../actors/token_manager" }
fs_manager = { path = "../actors/fs_manager" }
actix = { version = "0.13", features = [] } actix = { version = "0.13", features = [] }
actix-rt = { version = "2.7", features = [] } actix-rt = { version = "2.7", features = [] }

View File

@ -57,6 +57,9 @@ async fn server(opts: ServerOpts) -> Result<()> {
.expect("Failed to start payment manager") .expect("Failed to start payment manager")
.start(); .start();
let search_manager = search_manager::SearchManager::new(app_config.clone()); let search_manager = search_manager::SearchManager::new(app_config.clone());
let fs_manager = fs_manager::FsManager::build(app_config.clone())
.await
.expect("Failed to initialize file system storage");
let addr = { let addr = {
let l = app_config.lock(); let l = app_config.lock();
let w = l.web(); let w = l.web();
@ -84,6 +87,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
.app_data(Data::new(order_manager.clone())) .app_data(Data::new(order_manager.clone()))
.app_data(Data::new(payment_manager.clone())) .app_data(Data::new(payment_manager.clone()))
.app_data(Data::new(search_manager.clone())) .app_data(Data::new(search_manager.clone()))
.app_data(Data::new(fs_manager.clone()))
.configure(routes::configure) .configure(routes::configure)
.service({ .service({
let l = app_config.lock(); let l = app_config.lock();

View File

@ -1,646 +0,0 @@
pub mod api;
use std::fmt::Formatter;
use std::str::FromStr;
use derive_more::{Deref, Display, From};
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, thiserror::Error)]
pub enum TransformError {
#[error("Given value is below minimal value")]
BelowMinimal,
#[error("Given value is not valid day flag")]
NotDay,
#[error("Given value is not valid email address")]
NotEmail,
}
pub type RecordId = i32;
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
#[sqlx(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum OrderStatus {
#[display(fmt = "Potwierdzone")]
Confirmed,
#[display(fmt = "Odebrane")]
Delivered,
#[display(fmt = "Opłacone")]
Payed,
#[display(fmt = "Anulowane")]
Cancelled,
#[display(fmt = "Wymaga zwrotu płatności")]
RequireRefund,
#[display(fmt = "Płatność zwrócona")]
Refunded,
}
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)]
#[sqlx(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum Role {
#[display(fmt = "Adminitrator")]
Admin,
#[display(fmt = "Użytkownik")]
User,
}
impl PartialEq<&str> for Role {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl Role {
pub fn as_str(&self) -> &str {
match self {
Role::Admin => "Admin",
Role::User => "User",
}
}
}
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum QuantityUnit {
#[sqlx(rename = "g")]
Gram,
#[sqlx(rename = "dkg")]
Decagram,
#[sqlx(rename = "kg")]
Kilogram,
#[sqlx(rename = "piece")]
Piece,
}
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
#[sqlx(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum PaymentMethod {
PayU,
PaymentOnTheSpot,
}
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize)]
#[sqlx(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ShoppingCartState {
Active,
Closed,
}
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)]
#[sqlx(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum Audience {
Web,
Mobile,
Feed,
AdminPanel,
}
impl PartialEq<&str> for Audience {
fn eq(&self, other: &&str) -> bool {
self.as_str() == *other
}
}
impl Audience {
pub fn as_str(&self) -> &str {
match self {
Audience::Web => "Web",
Audience::Mobile => "Mobile",
Audience::Feed => "Feed",
Audience::AdminPanel => "AdminPanel",
}
}
}
#[derive(sqlx::Type, Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)]
#[sqlx(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum AccountState {
Active,
Suspended,
Banned,
}
impl Default for Audience {
fn default() -> Self {
Self::Web
}
}
#[derive(sqlx::Type, Serialize, Deserialize, Default, Debug, Copy, Clone, Deref, From)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct Price(NonNegative);
#[derive(sqlx::Type, Serialize, Deserialize, Default, Debug, Copy, Clone, Deref, From)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct Quantity(NonNegative);
impl TryFrom<i32> for Quantity {
type Error = TransformError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
Ok(Self(value.try_into()?))
}
}
#[derive(sqlx::Type, Deserialize, Serialize, Debug, Deref, From, Display)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct Login(String);
#[derive(sqlx::Type, Serialize, Debug, Deref, From, Display)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct Email(String);
impl FromStr for Email {
type Err = TransformError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if validator::validate_email(s) {
Ok(Self(String::from(s)))
} else {
Err(TransformError::NotEmail)
}
}
}
impl<'de> serde::Deserialize<'de> for Email {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct EmailVisitor {}
impl<'v> Visitor<'v> for EmailVisitor {
type Value = String;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("valid e-mail address")
}
fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: Error,
{
if validator::validate_email(s) {
Ok(String::from(s))
} else {
Err(E::custom("this is not email address"))
}
}
}
Ok(Email(deserializer.deserialize_str(EmailVisitor {})?))
}
}
#[derive(sqlx::Type, Serialize, Default, Debug, Copy, Clone, Deref, Display)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct NonNegative(i32);
impl TryFrom<i32> for NonNegative {
type Error = TransformError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value < 0 {
Err(TransformError::BelowMinimal)
} else {
Ok(Self(value))
}
}
}
impl<'de> serde::Deserialize<'de> for NonNegative {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct NonNegativeVisitor;
impl<'v> Visitor<'v> for NonNegativeVisitor {
type Value = i32;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("value equal or greater than 0")
}
fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
where
E: Error,
{
if v >= 0 {
Ok(v)
} else {
Err(E::custom("Value must be equal or greater than 0"))
}
}
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: Error,
{
let v = v
.try_into()
.map_err(|_| E::custom("Value must be equal or greater than 0"))?;
if v >= 0 {
Ok(v)
} else {
Err(E::custom("Value must be equal or greater than 0"))
}
}
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where
E: Error,
{
let v = v
.try_into()
.map_err(|_| E::custom("Value must be equal or greater than 0"))?;
Ok(v)
}
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
where
E: Error,
{
let v = v
.try_into()
.map_err(|_| E::custom("Value must be equal or greater than 0"))?;
Ok(v)
}
}
Ok(NonNegative(
deserializer.deserialize_i32(NonNegativeVisitor)?,
))
}
}
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Display, From)]
#[serde(rename_all = "lowercase")]
pub enum Day {
Monday = 1 << 0,
Tuesday = 1 << 1,
Wednesday = 1 << 2,
Thursday = 1 << 3,
Friday = 1 << 4,
Saturday = 1 << 5,
Sunday = 1 << 6,
}
impl TryFrom<i32> for Day {
type Error = TransformError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value == (Day::Monday as i32) {
Ok(Day::Monday)
} else if value == (Day::Tuesday as i32) {
Ok(Day::Tuesday)
} else if value == (Day::Wednesday as i32) {
Ok(Day::Wednesday)
} else if value == (Day::Thursday as i32) {
Ok(Day::Thursday)
} else if value == (Day::Friday as i32) {
Ok(Day::Friday)
} else if value == (Day::Saturday as i32) {
Ok(Day::Saturday)
} else if value == (Day::Sunday as i32) {
Ok(Day::Sunday)
} else {
Err(TransformError::NotDay)
}
}
}
#[derive(Serialize, Deserialize, Deref, Debug)]
#[serde(transparent)]
pub struct Days(Vec<Day>);
impl<'q> ::sqlx::encode::Encode<'q, sqlx::Postgres> for Days
where
i32: ::sqlx::encode::Encode<'q, sqlx::Postgres>,
{
fn encode_by_ref(
&self,
buf: &mut <sqlx::Postgres as ::sqlx::database::HasArguments<'q>>::ArgumentBuffer,
) -> ::sqlx::encode::IsNull {
let value = self.0.iter().fold(1, |memo, v| memo | *v as i32);
<i32 as ::sqlx::encode::Encode<sqlx::Postgres>>::encode_by_ref(&value, buf)
}
fn size_hint(&self) -> usize {
<i32 as ::sqlx::encode::Encode<sqlx::Postgres>>::size_hint(&Default::default())
}
}
impl<'r> ::sqlx::decode::Decode<'r, sqlx::Postgres> for Days
where
i32: ::sqlx::decode::Decode<'r, sqlx::Postgres>,
{
fn decode(
value: <sqlx::Postgres as ::sqlx::database::HasValueRef<'r>>::ValueRef,
) -> ::std::result::Result<
Self,
::std::boxed::Box<
dyn ::std::error::Error + 'static + ::std::marker::Send + ::std::marker::Sync,
>,
> {
let value = <i32 as ::sqlx::decode::Decode<'r, sqlx::Postgres>>::decode(value)?;
Ok(Days(
(0..9)
.into_iter()
.filter_map(|n| {
eprintln!(
"d {} {} {} {:?}",
n,
1 << n,
value & 1 << n,
Day::try_from(value & 1 << n).ok()
);
Day::try_from(value & 1 << n).ok()
})
.collect(),
))
}
}
impl sqlx::Type<sqlx::Postgres> for Days
where
i32: ::sqlx::Type<sqlx::Postgres>,
{
fn type_info() -> <sqlx::Postgres as ::sqlx::Database>::TypeInfo {
<i32 as ::sqlx::Type<sqlx::Postgres>>::type_info()
}
fn compatible(ty: &<sqlx::Postgres as ::sqlx::Database>::TypeInfo) -> bool {
<i32 as ::sqlx::Type<sqlx::Postgres>>::compatible(ty)
}
}
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Deref, From, Display)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct Password(String);
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Deref, From, Display)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct PasswordConfirmation(String);
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Deref, From, Display)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct PassHash(String);
impl PartialEq<PasswordConfirmation> for Password {
fn eq(&self, other: &PasswordConfirmation) -> bool {
self.0 == other.0
}
}
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref, Display, From)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct AccountId(RecordId);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct FullAccount {
pub id: AccountId,
pub email: Email,
pub login: Login,
pub pass_hash: PassHash,
pub role: Role,
pub customer_id: uuid::Uuid,
pub state: AccountState,
}
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct Account {
pub id: AccountId,
pub email: Email,
pub login: Login,
pub role: Role,
pub customer_id: uuid::Uuid,
pub state: AccountState,
}
impl From<FullAccount> for Account {
fn from(
FullAccount {
id,
email,
login,
pass_hash: _,
role,
customer_id,
state,
}: FullAccount,
) -> Self {
Self {
id,
email,
login,
role,
customer_id,
state,
}
}
}
#[derive(
sqlx::Type,
Serialize,
Deserialize,
Debug,
Copy,
Clone,
PartialEq,
Eq,
Hash,
Deref,
Display,
From,
)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct ProductId(RecordId);
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct ProductName(String);
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct ProductShortDesc(String);
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct ProductLongDesc(String);
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Clone, Deref, Display, From)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct ProductCategory(String);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct Product {
pub id: ProductId,
pub name: ProductName,
pub short_description: ProductShortDesc,
pub long_description: ProductLongDesc,
pub category: Option<ProductCategory>,
pub price: Price,
pub deliver_days_flag: Days,
}
#[derive(sqlx::Type, Serialize, Deserialize)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct StockId(pub RecordId);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct Stock {
pub id: StockId,
pub product_id: ProductId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Display, Deref)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct AccountOrderId(RecordId);
#[derive(sqlx::Type, Serialize, Deserialize, Clone, Debug, PartialEq, Display, Deref)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct OrderId(String);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct AccountOrder {
pub id: AccountOrderId,
pub buyer_id: AccountId,
pub status: OrderStatus,
pub order_id: Option<OrderId>,
pub order_ext_id: uuid::Uuid,
pub service_order_id: Option<String>,
}
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct PublicAccountOrder {
pub id: AccountOrderId,
pub buyer_id: AccountId,
pub status: OrderStatus,
pub order_id: Option<OrderId>,
}
impl From<AccountOrder> for PublicAccountOrder {
fn from(
AccountOrder {
id,
buyer_id,
status,
order_id,
order_ext_id: _,
service_order_id: _,
}: AccountOrder,
) -> Self {
Self {
id,
buyer_id,
status,
order_id,
}
}
}
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct OrderItemId(pub RecordId);
#[derive(sqlx::FromRow, Serialize, Deserialize, Debug)]
pub struct OrderItem {
pub id: OrderItemId,
pub product_id: ProductId,
pub order_id: AccountOrderId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref, Display)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct ShoppingCartId(pub RecordId);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct ShoppingCart {
pub id: ShoppingCartId,
pub buyer_id: AccountId,
pub payment_method: PaymentMethod,
pub state: ShoppingCartState,
}
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Debug, Deref, Display)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct ShoppingCartItemId(RecordId);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct ShoppingCartItem {
pub id: ShoppingCartId,
pub product_id: ProductId,
pub shopping_cart_id: ShoppingCartId,
pub quantity: Quantity,
pub quantity_unit: QuantityUnit,
}
impl ShoppingCartItem {
pub fn quantity(&self) -> Quantity {
self.quantity
}
}
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display, Debug)]
#[sqlx(transparent)]
#[serde(transparent)]
pub struct TokenId(RecordId);
#[derive(sqlx::FromRow, Serialize, Deserialize)]
pub struct Token {
pub id: TokenId,
pub customer_id: uuid::Uuid,
pub role: Role,
/// iss (issuer): Issuer of the JWT
pub issuer: String,
/// sub (subject): Subject of the JWT (the user)
pub subject: i32,
/// aud (audience): Recipient for which the JWT is intended
pub audience: Audience,
/// exp (expiration time): Time after which the JWT expires
pub expiration_time: chrono::NaiveDateTime,
/// nbt (not before time): Time before which the JWT must not be accepted
/// for processing
pub not_before_time: chrono::NaiveDateTime,
/// iat (issued at time): Time at which the JWT was issued; can be used to
/// determine age of the JWT,
pub issued_at_time: chrono::NaiveDateTime,
/// jti (JWT ID): Unique identifier; can be used to prevent the JWT from
/// being replayed (allows a token to be used only once)
pub jwt_id: uuid::Uuid,
}
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Deref, Display, From)]
pub struct TokenString(String);

View File

@ -1,126 +0,0 @@
use serde::Serialize;
use crate::model;
use crate::model::{AccountId, AccountOrderId, OrderId, OrderItem, OrderStatus};
#[derive(Serialize, Debug)]
#[serde(transparent)]
pub struct AccountOrders(pub Vec<AccountOrder>);
impl From<(Vec<model::AccountOrder>, Vec<model::OrderItem>)> for AccountOrders {
fn from((orders, mut items): (Vec<model::AccountOrder>, Vec<model::OrderItem>)) -> Self {
Self(
orders
.into_iter()
.map(
|model::AccountOrder {
id,
buyer_id,
status,
order_id,
order_ext_id: _,
service_order_id: _,
}| {
AccountOrder {
id,
buyer_id,
status,
order_id,
items: items.drain_filter(|item| item.order_id == id).collect(),
}
},
)
.collect(),
)
}
}
impl From<(model::AccountOrder, Vec<model::OrderItem>)> for AccountOrder {
fn from(
(
model::AccountOrder {
id,
buyer_id,
status,
order_id,
order_ext_id: _,
service_order_id: _,
},
mut items,
): (model::AccountOrder, Vec<model::OrderItem>),
) -> Self {
AccountOrder {
id,
buyer_id,
status,
order_id,
items: items.drain_filter(|item| item.order_id == id).collect(),
}
}
}
#[derive(Serialize, Debug)]
pub struct AccountOrder {
pub id: AccountOrderId,
pub buyer_id: AccountId,
pub status: OrderStatus,
pub order_id: Option<OrderId>,
pub items: Vec<OrderItem>,
}
#[derive(Serialize, Debug)]
pub struct ShoppingCartItem {
pub id: model::ShoppingCartId,
pub product_id: model::ProductId,
pub shopping_cart_id: model::ShoppingCartId,
pub quantity: model::Quantity,
pub quantity_unit: model::QuantityUnit,
}
#[derive(Serialize, Debug)]
pub struct ShoppingCart {
pub id: model::ShoppingCartId,
pub buyer_id: AccountId,
pub payment_method: model::PaymentMethod,
pub state: model::ShoppingCartState,
pub items: Vec<ShoppingCartItem>,
}
impl From<(model::ShoppingCart, Vec<model::ShoppingCartItem>)> for ShoppingCart {
fn from(
(
model::ShoppingCart {
id,
buyer_id,
payment_method,
state,
},
items,
): (model::ShoppingCart, Vec<model::ShoppingCartItem>),
) -> Self {
Self {
id,
buyer_id,
payment_method,
state,
items: items
.into_iter()
.map(
|model::ShoppingCartItem {
id,
product_id,
shopping_cart_id,
quantity,
quantity_unit,
}| ShoppingCartItem {
id,
product_id,
shopping_cart_id,
quantity,
quantity_unit,
},
)
.collect(),
}
}
}

View File

@ -1,8 +1,111 @@
use actix_web::web::ServiceConfig; use actix::Addr;
use actix_multipart::Multipart;
use actix_web::web::{Data, ServiceConfig};
use actix_web::{post, HttpResponse}; use actix_web::{post, HttpResponse};
use database_manager::{query_db, Database};
use fs_manager::FsManager;
use futures_util::StreamExt;
#[derive(Debug, thiserror::Error)]
pub enum UploadError {
#[error("Uploaded file name is invalid")]
InvalidName,
#[error("Write to disk failed")]
FileStreamBroken,
#[error("Failed to save in database")]
DbSave,
}
#[post("/product-image")] #[post("/product-image")]
async fn upload_product_image() -> HttpResponse { async fn upload_product_image(
mut payload: Multipart,
fs: Data<Addr<FsManager>>,
db: Data<Addr<Database>>,
) -> HttpResponse {
let mut name = None;
while let Some(Ok(mut field)) = payload.next().await {
let field_name = field.name();
match field_name {
"name" => {
if name.is_some() {
return HttpResponse::BadRequest().finish();
}
let mut value = String::with_capacity(300);
while let Some(Ok(bytes)) = field.next().await {
if let Ok(s) = std::str::from_utf8(&bytes) {
value.push_str(s);
} else {
return HttpResponse::BadRequest().finish();
}
}
name = Some(value);
}
"photo" => {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let msg = fs_manager::WriteFile {
file_name: match name.take() {
Some(name) => name,
_ => return HttpResponse::BadRequest().finish(),
},
stream: rx,
};
let read = async {
while let Some(Ok(data)) = field.next().await {
for b in data {
if let Err(e) = tx.send(b) {
log::error!("{e:?}");
return Err(UploadError::InvalidName);
}
}
}
Ok(())
};
let write = async {
let fs_manager::WriteResult {
local_path,
file_name,
} = match fs.send(msg).await {
Ok(Ok(file_name)) => file_name,
Ok(Err(e)) => {
log::error!("{e}");
return Err(UploadError::FileStreamBroken);
}
Err(e) => {
log::error!("{e}");
return Err(UploadError::FileStreamBroken);
}
};
query_db!(
db,
database_manager::CreatePhoto {
local_path,
file_name
},
UploadError::DbSave
);
Ok(())
};
match tokio::join!(read, write) {
(Ok(_), Ok(_)) => {}
(Ok(_), Err(e)) => {
log::error!("Write error. {e}");
return HttpResponse::BadRequest().finish();
}
(Err(e), Ok(_)) => {
log::error!("Read error. {e:?}");
return HttpResponse::BadRequest().finish();
}
(Err(read), Err(write)) => {
log::error!("Read error. {read:?}");
log::error!("Write error. {write:?}");
return HttpResponse::BadRequest().finish();
}
}
}
_ => {}
}
}
HttpResponse::NotImplemented().finish() HttpResponse::NotImplemented().finish()
} }

21
db-seed/Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "db-seed"
version = "0.1.0"
edition = "2021"
[dependencies]
model = { path = "../shared/model", version = "0.1", features = ["db", "dummy"] }
config = { path = "../shared/config" }
database_manager = { path = "../actors/database_manager", features = ["dummy"] }
actix = { version = "0.13", features = [] }
actix-rt = { version = "2.7", features = [] }
actix-web = { version = "4.0", features = [] }
fake = { version = "2.4.3", features = ["derive", "chrono", "http"] }
rand = { version = "0.8.5" }
dotenv = { version = "0.15", features = [] }
log = { version = "0.4", features = [] }
pretty_env_logger = { version = "0.4", features = [] }

37
db-seed/src/main.rs Normal file
View File

@ -0,0 +1,37 @@
use actix::Actor;
use config::{AppConfig, UpdateConfig};
use fake::{Fake, Faker};
pub struct Opts;
impl UpdateConfig for Opts {
fn update_config(&self, _config: &mut AppConfig) {}
}
#[actix_web::main]
async fn main() {
dotenv::dotenv().ok();
std::env::set_var("RUST_LOG", "DEBUG");
pretty_env_logger::init();
let config = config::default_load(&Opts);
let db = database_manager::Database::build(config)
.await
.unwrap()
.start();
let mut users = Vec::with_capacity(10);
for _ in 0..10 {
match db
.send(Faker.fake::<database_manager::CreateAccount>())
.await
{
Ok(Ok(user)) => users.push(user),
Ok(Err(e)) => {
log::error!("{e}")
}
Err(e) => {
log::error!("{e}")
}
}
}
}

View File

@ -0,0 +1,2 @@
ALTER TABLE photos
RENAME COLUMN hashed_path TO file_name;

View File

@ -5,6 +5,7 @@ edition = "2021"
[features] [features]
db = ["sqlx", "sqlx-core"] db = ["sqlx", "sqlx-core"]
dummy = ["fake", "rand"]
[dependencies] [dependencies]
serde = { version = "1.0.137" } serde = { version = "1.0.137" }
@ -20,3 +21,6 @@ derive_more = { version = "0.99.17" }
thiserror = { version = "1.0.31" } 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 }
rand = { version = "0.8.5", optional = true }

View File

@ -6,6 +6,8 @@ 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")]
use fake::Fake;
use serde::de::{Error, Visitor}; use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
@ -21,6 +23,7 @@ pub enum TransformError {
pub type RecordId = i32; pub type RecordId = i32;
#[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(rename_all = "snake_case"))] #[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)] #[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)]
@ -40,6 +43,7 @@ pub enum OrderStatus {
Refunded, Refunded,
} }
#[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(rename_all = "snake_case"))] #[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)] #[derive(Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)]
@ -66,6 +70,7 @@ impl Role {
} }
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))] #[cfg_attr(feature = "db", derive(sqlx::Type))]
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)] #[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -80,6 +85,7 @@ pub enum QuantityUnit {
Piece, Piece,
} }
#[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(rename_all = "snake_case"))] #[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)] #[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)]
@ -89,6 +95,7 @@ pub enum PaymentMethod {
PaymentOnTheSpot, PaymentOnTheSpot,
} }
#[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(rename_all = "snake_case"))] #[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)] #[derive(Copy, Clone, Debug, Display, Deserialize, Serialize)]
@ -98,6 +105,7 @@ pub enum ShoppingCartState {
Closed, Closed,
} }
#[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(rename_all = "snake_case"))] #[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)] #[derive(Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)]
@ -126,6 +134,7 @@ impl 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(rename_all = "snake_case"))] #[cfg_attr(feature = "db", sqlx(rename_all = "snake_case"))]
#[derive(Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)] #[derive(Copy, Clone, Debug, Display, Deserialize, Serialize, PartialEq)]
@ -142,12 +151,14 @@ 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)]
#[serde(transparent)] #[serde(transparent)]
pub struct Price(NonNegative); pub struct Price(NonNegative);
#[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)]
@ -162,12 +173,16 @@ 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)]
@ -215,6 +230,7 @@ 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)]
@ -298,6 +314,7 @@ impl<'de> serde::Deserialize<'de> for NonNegative {
} }
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Debug, Copy, Clone, Display, From)] #[derive(Serialize, Deserialize, Debug, Copy, Clone, Display, From)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum Day { pub enum Day {
@ -334,6 +351,7 @@ impl TryFrom<i32> for Day {
} }
} }
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[derive(Serialize, Deserialize, Deref, Debug)] #[derive(Serialize, Deserialize, Deref, Debug)]
#[serde(transparent)] #[serde(transparent)]
pub struct Days(Vec<Day>); pub struct Days(Vec<Day>);
@ -403,18 +421,21 @@ 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);
#[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 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)]
@ -427,12 +448,14 @@ impl PartialEq<PasswordConfirmation> for Password {
} }
} }
#[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, Copy, Clone, Debug, Deref, Display, From)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, Deref, Display, From)]
#[serde(transparent)] #[serde(transparent)]
pub struct AccountId(RecordId); pub struct AccountId(RecordId);
#[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)]
pub struct FullAccount { pub struct FullAccount {
@ -445,6 +468,7 @@ pub struct FullAccount {
pub state: AccountState, pub state: AccountState,
} }
#[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)]
pub struct Account { pub struct Account {
@ -479,36 +503,42 @@ impl From<FullAccount> for Account {
} }
} }
#[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, Copy, Clone, PartialEq, Eq, Hash, Deref, Display, From)] #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash, Deref, Display, From)]
#[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)]
#[serde(transparent)] #[serde(transparent)]
pub struct ProductLongDesc(String); pub struct ProductLongDesc(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 ProductCategory(String); pub struct ProductCategory(String);
#[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)]
pub struct Product { pub struct Product {
@ -521,12 +551,14 @@ pub struct Product {
pub deliver_days_flag: Days, pub deliver_days_flag: Days,
} }
#[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)] #[derive(Serialize, Deserialize)]
#[serde(transparent)] #[serde(transparent)]
pub struct StockId(pub RecordId); pub struct StockId(pub RecordId);
#[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)]
pub struct Stock { pub struct Stock {
@ -536,18 +568,21 @@ pub struct Stock {
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
} }
#[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, Copy, Clone, Debug, PartialEq, Display, Deref)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Display, Deref)]
#[serde(transparent)] #[serde(transparent)]
pub struct AccountOrderId(RecordId); pub struct AccountOrderId(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, Clone, Debug, PartialEq, Display, Deref)] #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Display, Deref)]
#[serde(transparent)] #[serde(transparent)]
pub struct OrderId(String); pub struct OrderId(String);
#[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)]
pub struct AccountOrder { pub struct AccountOrder {
@ -559,6 +594,7 @@ pub struct AccountOrder {
pub service_order_id: Option<String>, pub service_order_id: Option<String>,
} }
#[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)]
pub struct PublicAccountOrder { pub struct PublicAccountOrder {
@ -588,11 +624,13 @@ impl From<AccountOrder> for PublicAccountOrder {
} }
} }
#[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, Copy, Clone, Debug, Deref)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, Deref)]
pub struct OrderItemId(pub RecordId); pub struct OrderItemId(pub RecordId);
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::FromRow))] #[cfg_attr(feature = "db", derive(sqlx::FromRow))]
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct OrderItem { pub struct OrderItem {
@ -603,12 +641,14 @@ pub struct OrderItem {
pub quantity_unit: QuantityUnit, pub quantity_unit: QuantityUnit,
} }
#[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, Copy, Clone, Debug, Deref, Display)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, Deref, Display)]
#[serde(transparent)] #[serde(transparent)]
pub struct ShoppingCartId(pub RecordId); pub struct ShoppingCartId(pub RecordId);
#[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)]
pub struct ShoppingCart { pub struct ShoppingCart {
@ -618,6 +658,7 @@ pub struct ShoppingCart {
pub state: ShoppingCartState, pub state: ShoppingCartState,
} }
#[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, Copy, Clone, Debug, Deref, Display)] #[derive(Serialize, Deserialize, Copy, Clone, Debug, Deref, Display)]
@ -640,12 +681,14 @@ impl ShoppingCartItem {
} }
} }
#[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, Copy, Clone, Deref, Display, Debug)] #[derive(Serialize, Deserialize, Copy, Clone, Deref, Display, Debug)]
#[serde(transparent)] #[serde(transparent)]
pub struct TokenId(RecordId); pub struct TokenId(RecordId);
#[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)]
pub struct Token { pub struct Token {
@ -671,12 +714,50 @@ pub struct Token {
pub jwt_id: uuid::Uuid, pub jwt_id: uuid::Uuid,
} }
#[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, Deref, Display, From)]
pub struct TokenString(String); pub struct TokenString(String);
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))]
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
pub struct LocalPath(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, Display, From)] #[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
pub struct FileName(String); pub struct FileName(String);
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))]
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
pub struct PhotoId(RecordId);
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::Type))]
#[cfg_attr(feature = "db", sqlx(transparent))]
#[derive(Serialize, Deserialize, Debug, Deref, Display, From)]
pub struct ProductPhotoId(RecordId);
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
#[derive(Serialize, Deserialize, Debug)]
pub struct Photo {
pub id: PhotoId,
pub local_path: LocalPath,
pub file_name: FileName,
}
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
#[derive(Serialize, Deserialize, Debug)]
pub struct ProductPhoto {
pub id: ProductPhotoId,
pub product_id: ProductId,
pub photo_id: PhotoId,
}