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",
|
||||
"config",
|
||||
"fake",
|
||||
"itertools",
|
||||
"log",
|
||||
"model",
|
||||
"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 }
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
//######################################
|
||||
// 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};
|
||||
|
||||
pub mod logic;
|
||||
mod opts;
|
||||
pub mod routes;
|
||||
|
||||
|
@ -15,8 +15,7 @@ use crate::routes::{RequireLogin, Result};
|
||||
#[macro_export]
|
||||
macro_rules! admin_send_db {
|
||||
($db: expr, $msg: expr) => {{
|
||||
let db = $db;
|
||||
match db.send($msg).await {
|
||||
match $db.send($msg).await {
|
||||
Ok(Ok(res)) => res,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{}", e);
|
||||
|
@ -4,7 +4,7 @@ use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web::{delete, get, patch, post, HttpResponse};
|
||||
use database_manager::Database;
|
||||
use model::{
|
||||
Days, Price, ProductCategory, ProductId, ProductLongDesc, ProductName, ProductShortDesc,
|
||||
api, Days, Price, ProductCategory, ProductId, ProductLongDesc, ProductName, ProductShortDesc,
|
||||
Quantity, QuantityUnit,
|
||||
};
|
||||
use search_manager::SearchManager;
|
||||
@ -15,11 +15,22 @@ use crate::routes::RequireLogin;
|
||||
use crate::{admin_send_db, routes};
|
||||
|
||||
#[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()?;
|
||||
|
||||
let db = db.into_inner();
|
||||
|
||||
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)]
|
||||
|
@ -9,8 +9,7 @@ macro_rules! public_send_db {
|
||||
($db: expr, $msg: expr) => {{
|
||||
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(Err(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)]
|
||||
|
@ -3,7 +3,7 @@ use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web::{get, post, HttpResponse};
|
||||
use config::SharedAppConfig;
|
||||
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 token_manager::TokenManager;
|
||||
|
||||
@ -12,8 +12,18 @@ use crate::routes::{self, Result};
|
||||
use crate::{public_send_db, Login, Password};
|
||||
|
||||
#[get("/products")]
|
||||
async fn products(db: Data<Addr<Database>>) -> Result<HttpResponse> {
|
||||
public_send_db!(db.into_inner(), database_manager::AllProducts)
|
||||
async fn products(db: Data<Addr<Database>>) -> Result<Json<api::Products>> {
|
||||
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")]
|
||||
|
@ -1,5 +1,7 @@
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::ProductLinkedPhoto;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
#[serde(transparent)]
|
||||
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,
|
||||
}
|
||||
|
||||
#[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 = "db", derive(sqlx::FromRow))]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
Loading…
Reference in New Issue
Block a user