Product with photos
This commit is contained in:
parent
01d5dde052
commit
15363fc19d
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1117,6 +1117,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"config",
|
"config",
|
||||||
"fake",
|
"fake",
|
||||||
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"model",
|
"model",
|
||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
|
@ -26,3 +26,5 @@ pretty_env_logger = { version = "0.4", features = [] }
|
|||||||
|
|
||||||
fake = { version = "2.4.3", features = ["derive", "chrono", "http", "uuid"], optional = true }
|
fake = { version = "2.4.3", features = ["derive", "chrono", "http", "uuid"], optional = true }
|
||||||
rand = { version = "0.8.5", optional = true }
|
rand = { version = "0.8.5", optional = true }
|
||||||
|
|
||||||
|
itertools = { version = "0.10.3" }
|
||||||
|
@ -63,3 +63,67 @@ RETURNING id, local_path, file_name
|
|||||||
crate::Error::Photo(Error::Create)
|
crate::Error::Photo(Error::Create)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//######################################
|
||||||
|
// SPECIAL CASES
|
||||||
|
//######################################
|
||||||
|
|
||||||
|
#[derive(actix::Message)]
|
||||||
|
#[rtype(result = "Result<Vec<model::ProductLinkedPhoto>>")]
|
||||||
|
pub struct PhotosForProducts {
|
||||||
|
pub product_ids: Vec<model::ProductId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::db_async_handler!(
|
||||||
|
PhotosForProducts,
|
||||||
|
photos_for_products,
|
||||||
|
Vec<model::ProductLinkedPhoto>,
|
||||||
|
inner_photos_for_products
|
||||||
|
);
|
||||||
|
|
||||||
|
pub(crate) async fn photos_for_products(
|
||||||
|
msg: PhotosForProducts,
|
||||||
|
pool: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
|
) -> Result<Vec<model::ProductLinkedPhoto>> {
|
||||||
|
log::debug!("all product ids {:?}", msg.product_ids);
|
||||||
|
let mut res: Vec<model::ProductLinkedPhoto> = Vec::with_capacity(100);
|
||||||
|
|
||||||
|
let len = msg.product_ids.len() / 20;
|
||||||
|
for ids in msg.product_ids.into_iter().fold(
|
||||||
|
Vec::<Vec<model::ProductId>>::with_capacity(len),
|
||||||
|
|mut v, id| {
|
||||||
|
if matches!(v.last().map(|v| v.len()), Some(20) | None) {
|
||||||
|
v.push(Vec::with_capacity(20));
|
||||||
|
}
|
||||||
|
v.last_mut().unwrap().push(id);
|
||||||
|
v
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
log::debug!("scoped product ids {:?}", ids);
|
||||||
|
let query: String = r#"
|
||||||
|
SELECT photos.id, photos.local_path, photos.file_name, product_photos.product_id
|
||||||
|
FROM photos
|
||||||
|
INNER JOIN product_photos
|
||||||
|
ON photos.id = product_photos.photo_id
|
||||||
|
WHERE
|
||||||
|
"#
|
||||||
|
.into();
|
||||||
|
let query = ids.iter().enumerate().fold(query, |mut q, (idx, _id)| {
|
||||||
|
if idx != 0 {
|
||||||
|
q.push_str(" OR");
|
||||||
|
}
|
||||||
|
q.push_str(&format!(" product_photos.product_id = ${}", idx + 1));
|
||||||
|
q
|
||||||
|
});
|
||||||
|
let q = sqlx::query_as::<_, model::ProductLinkedPhoto>(query.as_str());
|
||||||
|
let q = ids.into_iter().map(|id| *id).fold(q, |q, id| q.bind(id));
|
||||||
|
|
||||||
|
let records = q.fetch_all(&mut *pool).await.map_err(|e| {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
crate::Error::Photo(Error::All)
|
||||||
|
})?;
|
||||||
|
res.extend(records);
|
||||||
|
}
|
||||||
|
log::debug!("product linked photos {:?}", res);
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
pub mod order_state;
|
|
@ -1 +0,0 @@
|
|||||||
|
|
@ -18,7 +18,6 @@ use opts::{
|
|||||||
};
|
};
|
||||||
use validator::{validate_email, validate_length};
|
use validator::{validate_email, validate_length};
|
||||||
|
|
||||||
pub mod logic;
|
|
||||||
mod opts;
|
mod opts;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
|
||||||
|
@ -15,8 +15,7 @@ use crate::routes::{RequireLogin, Result};
|
|||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! admin_send_db {
|
macro_rules! admin_send_db {
|
||||||
($db: expr, $msg: expr) => {{
|
($db: expr, $msg: expr) => {{
|
||||||
let db = $db;
|
match $db.send($msg).await {
|
||||||
match db.send($msg).await {
|
|
||||||
Ok(Ok(res)) => res,
|
Ok(Ok(res)) => res,
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
log::error!("{}", e);
|
log::error!("{}", e);
|
||||||
|
@ -4,7 +4,7 @@ use actix_web::web::{Data, Json, ServiceConfig};
|
|||||||
use actix_web::{delete, get, patch, post, HttpResponse};
|
use actix_web::{delete, get, patch, post, HttpResponse};
|
||||||
use database_manager::Database;
|
use database_manager::Database;
|
||||||
use model::{
|
use model::{
|
||||||
Days, Price, ProductCategory, ProductId, ProductLongDesc, ProductName, ProductShortDesc,
|
api, Days, Price, ProductCategory, ProductId, ProductLongDesc, ProductName, ProductShortDesc,
|
||||||
Quantity, QuantityUnit,
|
Quantity, QuantityUnit,
|
||||||
};
|
};
|
||||||
use search_manager::SearchManager;
|
use search_manager::SearchManager;
|
||||||
@ -15,11 +15,22 @@ use crate::routes::RequireLogin;
|
|||||||
use crate::{admin_send_db, routes};
|
use crate::{admin_send_db, routes};
|
||||||
|
|
||||||
#[get("/products")]
|
#[get("/products")]
|
||||||
async fn products(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
|
async fn products(
|
||||||
|
session: Session,
|
||||||
|
db: Data<Addr<Database>>,
|
||||||
|
) -> routes::Result<Json<api::Products>> {
|
||||||
session.require_admin()?;
|
session.require_admin()?;
|
||||||
|
|
||||||
|
let db = db.into_inner();
|
||||||
|
|
||||||
let products = admin_send_db!(db, database_manager::AllProducts);
|
let products = admin_send_db!(db, database_manager::AllProducts);
|
||||||
Ok(HttpResponse::Ok().json(products))
|
let photos = admin_send_db!(
|
||||||
|
db,
|
||||||
|
database_manager::PhotosForProducts {
|
||||||
|
product_ids: products.iter().map(|p| p.id).collect()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Ok(Json((products, photos).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
@ -9,8 +9,7 @@ macro_rules! public_send_db {
|
|||||||
($db: expr, $msg: expr) => {{
|
($db: expr, $msg: expr) => {{
|
||||||
use crate::routes::PublicError;
|
use crate::routes::PublicError;
|
||||||
|
|
||||||
let db = $db;
|
return match $db.send($msg).await {
|
||||||
return match db.send($msg).await {
|
|
||||||
Ok(Ok(res)) => Ok(HttpResponse::Ok().json(res)),
|
Ok(Ok(res)) => Ok(HttpResponse::Ok().json(res)),
|
||||||
Ok(Err(e)) => {
|
Ok(Err(e)) => {
|
||||||
log::error!("{}", e);
|
log::error!("{}", e);
|
||||||
@ -24,6 +23,23 @@ macro_rules! public_send_db {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}};
|
}};
|
||||||
|
(owned, $db: expr, $msg: expr) => {{
|
||||||
|
use crate::routes::PublicError;
|
||||||
|
|
||||||
|
match $db.send($msg).await {
|
||||||
|
Ok(Ok(res)) => res,
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("{}", e);
|
||||||
|
return Err(crate::routes::Error::Public(PublicError::Database(e)));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{}", e);
|
||||||
|
return Err(crate::routes::Error::Public(
|
||||||
|
PublicError::DatabaseConnection,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
@ -3,7 +3,7 @@ use actix_web::web::{Data, Json, ServiceConfig};
|
|||||||
use actix_web::{get, post, HttpResponse};
|
use actix_web::{get, post, HttpResponse};
|
||||||
use config::SharedAppConfig;
|
use config::SharedAppConfig;
|
||||||
use database_manager::{query_db, Database};
|
use database_manager::{query_db, Database};
|
||||||
use model::{Audience, Encrypt, FullAccount, Token, TokenString};
|
use model::{api, Audience, Encrypt, FullAccount, Token, TokenString};
|
||||||
use payment_manager::{PaymentManager, PaymentNotification};
|
use payment_manager::{PaymentManager, PaymentNotification};
|
||||||
use token_manager::TokenManager;
|
use token_manager::TokenManager;
|
||||||
|
|
||||||
@ -12,8 +12,18 @@ use crate::routes::{self, Result};
|
|||||||
use crate::{public_send_db, Login, Password};
|
use crate::{public_send_db, Login, Password};
|
||||||
|
|
||||||
#[get("/products")]
|
#[get("/products")]
|
||||||
async fn products(db: Data<Addr<Database>>) -> Result<HttpResponse> {
|
async fn products(db: Data<Addr<Database>>) -> Result<Json<api::Products>> {
|
||||||
public_send_db!(db.into_inner(), database_manager::AllProducts)
|
let db = db.into_inner();
|
||||||
|
|
||||||
|
let products: Vec<model::Product> = public_send_db!(owned, db, database_manager::AllProducts);
|
||||||
|
let photos: Vec<model::ProductLinkedPhoto> = public_send_db!(
|
||||||
|
owned,
|
||||||
|
db,
|
||||||
|
database_manager::PhotosForProducts {
|
||||||
|
product_ids: products.iter().map(|p| p.id).collect()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Ok(Json((products, photos).into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/stocks")]
|
#[get("/stocks")]
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::ProductLinkedPhoto;
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct AccountOrders(pub Vec<AccountOrder>);
|
pub struct AccountOrders(pub Vec<AccountOrder>);
|
||||||
@ -121,3 +123,74 @@ impl From<(crate::ShoppingCart, Vec<crate::ShoppingCartItem>)> for ShoppingCart
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct Photo {
|
||||||
|
pub id: crate::PhotoId,
|
||||||
|
pub file_name: crate::FileName,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct Product {
|
||||||
|
pub id: crate::ProductId,
|
||||||
|
pub name: crate::ProductName,
|
||||||
|
pub short_description: crate::ProductShortDesc,
|
||||||
|
pub long_description: crate::ProductLongDesc,
|
||||||
|
pub category: Option<crate::ProductCategory>,
|
||||||
|
pub price: crate::Price,
|
||||||
|
pub deliver_days_flag: crate::Days,
|
||||||
|
pub photos: Vec<Photo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(crate::Product, &mut Vec<crate::ProductLinkedPhoto>)> for Product {
|
||||||
|
fn from(
|
||||||
|
(
|
||||||
|
crate::Product {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
short_description,
|
||||||
|
long_description,
|
||||||
|
category,
|
||||||
|
price,
|
||||||
|
deliver_days_flag,
|
||||||
|
},
|
||||||
|
photos,
|
||||||
|
): (crate::Product, &mut Vec<ProductLinkedPhoto>),
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
short_description,
|
||||||
|
long_description,
|
||||||
|
category,
|
||||||
|
price,
|
||||||
|
deliver_days_flag,
|
||||||
|
photos: photos
|
||||||
|
.drain_filter(|photo| photo.product_id == id)
|
||||||
|
.map(
|
||||||
|
|ProductLinkedPhoto {
|
||||||
|
id,
|
||||||
|
local_path: _,
|
||||||
|
file_name,
|
||||||
|
product_id: _,
|
||||||
|
}| Photo { id, file_name },
|
||||||
|
)
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
pub struct Products(Vec<Product>);
|
||||||
|
|
||||||
|
impl From<(Vec<crate::Product>, Vec<crate::ProductLinkedPhoto>)> for Products {
|
||||||
|
fn from((products, mut photos): (Vec<crate::Product>, Vec<crate::ProductLinkedPhoto>)) -> Self {
|
||||||
|
Self(
|
||||||
|
products
|
||||||
|
.into_iter()
|
||||||
|
.map(|p| (p, &mut photos).into())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -767,6 +767,16 @@ pub struct Photo {
|
|||||||
pub file_name: FileName,
|
pub file_name: FileName,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
|
||||||
|
#[cfg_attr(feature = "db", derive(sqlx::FromRow))]
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct ProductLinkedPhoto {
|
||||||
|
pub id: PhotoId,
|
||||||
|
pub local_path: LocalPath,
|
||||||
|
pub file_name: FileName,
|
||||||
|
pub product_id: ProductId,
|
||||||
|
}
|
||||||
|
|
||||||
#[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, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
Loading…
Reference in New Issue
Block a user