diff options
Diffstat (limited to 'dichroism')
-rw-r--r-- | dichroism/src/dtos/mod.rs | 75 | ||||
-rw-r--r-- | dichroism/src/dtos/product_get.rs | 32 | ||||
-rw-r--r-- | dichroism/src/dtos/product_patch.rs | 11 | ||||
-rw-r--r-- | dichroism/src/dtos/product_post.rs | 10 | ||||
-rw-r--r-- | dichroism/src/handlers.rs | 101 | ||||
-rw-r--r-- | dichroism/src/image_service.rs | 66 | ||||
-rw-r--r-- | dichroism/src/main.rs | 5 | ||||
-rw-r--r-- | dichroism/src/models/dbid.rs | 4 | ||||
-rw-r--r-- | dichroism/src/models/photo.rs | 32 | ||||
-rw-r--r-- | dichroism/src/models/photo_set.rs | 57 | ||||
-rw-r--r-- | dichroism/src/models/product.rs | 7 | ||||
-rw-r--r-- | dichroism/src/repo/entities/mod.rs | 43 | ||||
-rw-r--r-- | dichroism/src/repo/entities/photo_set.rs | 22 | ||||
-rw-r--r-- | dichroism/src/repo/entities/photo_set_form.rs | 24 | ||||
-rw-r--r-- | dichroism/src/repo/entities/product.rs | 38 | ||||
-rw-r--r-- | dichroism/src/repo/entities/product_form.rs | 28 | ||||
-rw-r--r-- | dichroism/src/repo/mod.rs | 88 |
17 files changed, 433 insertions, 210 deletions
diff --git a/dichroism/src/dtos/mod.rs b/dichroism/src/dtos/mod.rs index d7e1ab5..cc8edd1 100644 --- a/dichroism/src/dtos/mod.rs +++ b/dichroism/src/dtos/mod.rs @@ -1,70 +1,7 @@ -use crate::models; -use crate::result::Result; +mod product_get; +mod product_patch; +mod product_post; -#[derive(Debug, Deserialize)] -pub struct NewProduct { - pub name: String, - pub quantity: u32, - pub cents: u32, - pub description: String, - pub featured: bool, - pub category_path: String, - pub photo_data: String, -} - -impl Into<Result<models::Product>> for NewProduct { - fn into(self) -> Result<models::Product> { - Ok(models::Product { - id: 0, - name: self.name, - quantity: self.quantity, - cents: self.cents, - description: self.description, - featured: self.featured, - category: self.category_path, - photo_set: models::PhotoSet::from_data_uri(&self.photo_data)?, - }) - } -} - -#[derive(Debug, Deserialize)] -pub struct ProductPatch { - pub name: Option<String>, - pub quantity: Option<i32>, - pub cents: Option<i32>, - pub description: Option<String>, - pub featured: Option<bool>, - pub category_path: Option<String>, - pub photo_data: Option<String>, -} - -#[derive(Debug, Serialize)] -pub struct Product { - pub id: u32, - pub name: String, - pub description: String, - pub cents: u32, - pub quantity: u32, - pub featured: bool, - pub photo_base: String, - pub photo_fullsize: String, - pub photo_thumbnail: String, - pub category: String, -} - -impl From<models::Product> for Product { - fn from(p: models::Product) -> Self { - Product { - id: p.id, - name: p.name, - description: p.description, - cents: p.cents, - quantity: p.quantity, - featured: p.featured, - category: p.category, - photo_fullsize: p.photo_set.fullsize.filename, - photo_base: p.photo_set.base.filename, - photo_thumbnail: p.photo_set.thumbnail.filename, - } - } -} +pub use product_get::*; +pub use product_patch::*; +pub use product_post::*; diff --git a/dichroism/src/dtos/product_get.rs b/dichroism/src/dtos/product_get.rs new file mode 100644 index 0000000..3f68eef --- /dev/null +++ b/dichroism/src/dtos/product_get.rs @@ -0,0 +1,32 @@ +use crate::models::Product; + +#[derive(Debug, Serialize)] +pub struct ProductGet { + pub id: i32, + pub name: String, + pub description: String, + pub cents: i32, + pub quantity: i32, + pub featured: bool, + pub photo_base: String, + pub photo_fullsize: String, + pub photo_thumbnail: String, + pub category: String, +} + +impl From<Product> for ProductGet { + fn from(p: Product) -> Self { + Self { + id: p.id.unwrap_or(-1), + name: p.name, + description: p.description, + cents: p.cents, + quantity: p.quantity, + featured: p.featured, + category: p.category, + photo_fullsize: p.photo_set.fullsize.filename(), + photo_base: p.photo_set.base.filename(), + photo_thumbnail: p.photo_set.thumbnail.filename(), + } + } +} diff --git a/dichroism/src/dtos/product_patch.rs b/dichroism/src/dtos/product_patch.rs new file mode 100644 index 0000000..ed8bf1a --- /dev/null +++ b/dichroism/src/dtos/product_patch.rs @@ -0,0 +1,11 @@ +#[derive(Debug, Deserialize)] +pub struct ProductPatch { + pub id: i32, + pub name: Option<String>, + pub quantity: Option<i32>, + pub cents: Option<i32>, + pub description: Option<String>, + pub featured: Option<bool>, + pub category_path: Option<String>, + pub photo_data: Option<String>, +} diff --git a/dichroism/src/dtos/product_post.rs b/dichroism/src/dtos/product_post.rs new file mode 100644 index 0000000..ef07536 --- /dev/null +++ b/dichroism/src/dtos/product_post.rs @@ -0,0 +1,10 @@ +#[derive(Debug, Deserialize)] +pub struct ProductPost { + pub name: String, + pub quantity: i32, + pub cents: i32, + pub description: String, + pub featured: bool, + pub category_path: String, + pub photo_data: String, +} diff --git a/dichroism/src/handlers.rs b/dichroism/src/handlers.rs index b4b117c..7c1d302 100644 --- a/dichroism/src/handlers.rs +++ b/dichroism/src/handlers.rs @@ -1,8 +1,15 @@ use crate::dtos::*; +use crate::image_service; +use crate::models::Product; use crate::repo; use crate::types::DbPool; use actix_web::{get, patch, post, web, Error, HttpResponse, Responder}; +fn to_internal_error(e: impl std::error::Error) -> HttpResponse { + eprintln!("{}", e); + HttpResponse::InternalServerError().body(e.to_string()) +} + #[get("/")] async fn hello() -> impl Responder { HttpResponse::Ok().body("Hey, this is an API!") @@ -10,34 +17,92 @@ async fn hello() -> impl Responder { #[get("/products")] async fn get_products(pool: web::Data<DbPool>) -> Result<HttpResponse, Error> { - let conn = pool.get().expect("Couldn't get DB connection from pool."); - let products: Vec<Product> = web::block(move || repo::read_products(&conn)) + let conn = pool.get().map_err(to_internal_error)?; + let products: Vec<ProductGet> = web::block(move || repo::find_all(&conn)) .await - .map_err(|e| { - eprintln!("{}", e); - HttpResponse::InternalServerError().body(e.to_string()) - })? + .map_err(to_internal_error)? .into_iter() .map(|p| p.into()) .collect(); Ok(HttpResponse::Ok().json(products)) } -#[patch("/products/{id}")] -async fn update_product( - _pool: web::Data<DbPool>, - id: web::Path<u32>, - updated_product: web::Json<ProductPatch>, +#[patch("/products")] +async fn patch_product( + pool: web::Data<DbPool>, + patch: web::Json<ProductPatch>, ) -> Result<HttpResponse, Error> { - dbg!(id, updated_product); - Ok(HttpResponse::Ok().finish()) + let patch = patch.into_inner(); + let id = patch.id; + + let conn = pool.get().map_err(to_internal_error)?; + let mut product = web::block(move || repo::find(&conn, id)) + .await + .map_err(to_internal_error)? + .ok_or_else(|| HttpResponse::NotFound().finish())?; + + if let Some(data_uri) = patch.photo_data { + // create new photo_set + let photo_set = image_service::generate_photo_set(&data_uri).map_err(|e| { + eprintln!("{}", e.to_string()); + HttpResponse::InternalServerError().body(e.to_string()) + })?; + let conn = pool.get().map_err(to_internal_error)?; + let photo_set = web::block(move || repo::store_photo_set(&conn, photo_set)) + .await + .map_err(to_internal_error)?; + product.photo_set = photo_set; + } + + // patch the rest of the product + product.name = patch.name.unwrap_or(product.name); + product.quantity = patch.quantity.unwrap_or(product.quantity); + product.cents = patch.cents.unwrap_or(product.cents); + product.description = patch.description.unwrap_or(product.description); + product.featured = patch.featured.unwrap_or(product.featured); + product.category = patch.category_path.unwrap_or(product.category); + + // store the updated product + let conn = pool.get().map_err(to_internal_error)?; + let product = web::block(move || repo::store_product(&conn, product)) + .await + .map_err(to_internal_error)?; + + Ok(HttpResponse::Ok().json(ProductGet::from(product))) } #[post("/products")] -async fn create_product( - _pool: web::Data<DbPool>, - new_product: web::Json<NewProduct>, +async fn post_product( + pool: web::Data<DbPool>, + post: web::Json<ProductPost>, ) -> Result<HttpResponse, Error> { - dbg!(new_product); - Ok(HttpResponse::Ok().finish()) + let post = post.into_inner(); + + // create new photo_set + let photo_set = image_service::generate_photo_set(&post.photo_data).map_err(|e| { + eprintln!("{}", e.to_string()); + HttpResponse::InternalServerError().body(e.to_string()) + })?; + let conn = pool.get().map_err(to_internal_error)?; + let photo_set = web::block(move || repo::store_photo_set(&conn, photo_set)) + .await + .map_err(to_internal_error)?; + + let product = Product { + id: None, + name: post.name, + quantity: post.quantity, + cents: post.cents, + description: post.description, + featured: post.featured, + category: post.category_path, + photo_set, + }; + + let conn = pool.get().map_err(to_internal_error)?; + let product = web::block(move || repo::store_product(&conn, product)) + .await + .map_err(to_internal_error)?; + + Ok(HttpResponse::Ok().json::<ProductGet>(product.into())) } diff --git a/dichroism/src/image_service.rs b/dichroism/src/image_service.rs new file mode 100644 index 0000000..b33d5c8 --- /dev/null +++ b/dichroism/src/image_service.rs @@ -0,0 +1,66 @@ +use crate::config::CONFIG_INSTANCE; +use crate::constants::{PHOTO_BASE_XY, PHOTO_FULLSIZE_XY, PHOTO_THUMBNAIL_XY}; +use crate::error::DichroismError; +use crate::models::{Photo, PhotoSet}; +use crate::result::Result; +use base64::decode; +use image::imageops::FilterType; +use image::DynamicImage; +use image::GenericImageView; +use once_cell::sync::Lazy; +use regex::Regex; +use std::path::PathBuf; +use uuid::Uuid; + +static DATA_URI_RE: Lazy<Regex> = Lazy::new(|| { + Regex::new("^data:image/(png|jpeg);base64,(?P<data>.+)") + .expect("Couldn't parse data URI Regex.") +}); + +// TODO: should be async so server threads don't block on FS access +pub fn generate_photo_set(uri: &str) -> Result<PhotoSet> { + let data = DATA_URI_RE + .captures(uri) + .ok_or(DichroismError::UriDataExtract)? + .name("data") + .ok_or(DichroismError::UriDataExtract)? + .as_str(); + let original = image::load_from_memory(&decode(data)?)?; + let fullsize = original.resize(PHOTO_FULLSIZE_XY, PHOTO_FULLSIZE_XY, FilterType::Lanczos3); + let base = original.resize(PHOTO_BASE_XY, PHOTO_BASE_XY, FilterType::Lanczos3); + + let (width, height) = original.dimensions(); + let thumbnail = if width > height { + let offset = (width - height) / 2; + original.crop_imm(offset, 0, width - offset * 2, height) + } else { + let offset = (height - width) / 2; + original.crop_imm(0, offset, width, height - offset * 2) + } + .resize(PHOTO_THUMBNAIL_XY, PHOTO_THUMBNAIL_XY, FilterType::Lanczos3); + + Ok(PhotoSet { + id: None, + original: generate_photo(&original)?, + fullsize: generate_photo(&fullsize)?, + base: generate_photo(&base)?, + thumbnail: generate_photo(&thumbnail)?, + }) +} + +pub fn generate_photo(image: &DynamicImage) -> Result<Photo> { + let base_name = Uuid::new_v3(&Uuid::NAMESPACE_OID, &image.to_bytes()) + .to_hyphenated() + .to_string(); + let mut path = PathBuf::from(&CONFIG_INSTANCE.img_root); + path.push(base_name); + path.set_extension("jpg"); + image.save(&path)?; + + let id = path + .file_name() + .ok_or(DichroismError::ImageWrite)? + .to_str() + .ok_or(DichroismError::ImageWrite)?; + Ok(Photo::new(String::from(id))) +} diff --git a/dichroism/src/main.rs b/dichroism/src/main.rs index c255717..68b7091 100644 --- a/dichroism/src/main.rs +++ b/dichroism/src/main.rs @@ -16,6 +16,7 @@ mod constants; mod dtos; mod error; mod handlers; +mod image_service; mod models; mod repo; mod result; @@ -34,8 +35,8 @@ async fn main() -> Result<()> { .data(pool.clone()) .service(handlers::hello) .service(handlers::get_products) - .service(handlers::update_product) - .service(handlers::create_product) + .service(handlers::patch_product) + .service(handlers::post_product) }); // If using listenfd, bind to it instead of the configured address to allow for cargo watch diff --git a/dichroism/src/models/dbid.rs b/dichroism/src/models/dbid.rs new file mode 100644 index 0000000..80eee58 --- /dev/null +++ b/dichroism/src/models/dbid.rs @@ -0,0 +1,4 @@ +pub enum Dbid { + Stored(i32), + NotStored, +} diff --git a/dichroism/src/models/photo.rs b/dichroism/src/models/photo.rs index 8c1435e..e24a691 100644 --- a/dichroism/src/models/photo.rs +++ b/dichroism/src/models/photo.rs @@ -1,34 +1,14 @@ -use crate::config::CONFIG_INSTANCE; -use crate::error::DichroismError; -use crate::result::Result; -use image::DynamicImage; -use std::path::PathBuf; -use uuid::Uuid; - -#[derive(Debug, Queryable, Serialize, Clone)] +#[derive(Debug, Clone)] pub struct Photo { - pub filename: String, + pub id: String, } impl Photo { - pub fn from_filename(filename: String) -> Self { - Self { filename } + pub fn new(id: String) -> Self { + Self { id } } - pub fn from_image(image: &DynamicImage) -> Result<Self> { - let base_name = Uuid::new_v3(&Uuid::NAMESPACE_OID, &image.to_bytes()) - .to_hyphenated() - .to_string(); - let mut path = PathBuf::from(&CONFIG_INSTANCE.img_root); - path.push(base_name); - path.set_extension("jpg"); - image.save(&path)?; - - let filename = path - .file_name() - .ok_or(DichroismError::ImageWrite)? - .to_str() - .ok_or(DichroismError::ImageWrite)?; - Ok(Self::from_filename(String::from(filename))) + pub fn filename(&self) -> String { + format!("{}.jpg", self.id) } } diff --git a/dichroism/src/models/photo_set.rs b/dichroism/src/models/photo_set.rs index 7187c65..f2c7677 100644 --- a/dichroism/src/models/photo_set.rs +++ b/dichroism/src/models/photo_set.rs @@ -1,61 +1,10 @@ -use super::photo::Photo; -use crate::constants::{PHOTO_BASE_XY, PHOTO_FULLSIZE_XY, PHOTO_THUMBNAIL_XY}; -use crate::error::DichroismError; -use crate::result::Result; -use base64::decode; -use image::imageops::FilterType; -use image::GenericImageView; -use once_cell::sync::Lazy; -use regex::Regex; - -static DATA_URI_RE: Lazy<Regex> = Lazy::new(|| { - Regex::new("^data:image/(png|jpeg);base64,(?P<data>.+)") - .expect("Couldn't parse data URI Regex.") -}); +use super::Photo; +#[derive(Debug, Clone)] pub struct PhotoSet { + pub id: Option<i32>, pub original: Photo, // original, just for safe-keeping pub fullsize: Photo, // full-size, "zoomed" view pub base: Photo, // basic viewing pub thumbnail: Photo, // tiny, square thumbnail } - -impl PhotoSet { - pub fn from_photos(original: Photo, fullsize: Photo, base: Photo, thumbnail: Photo) -> Self { - Self { - original, - fullsize, - base, - thumbnail, - } - } - - pub fn from_data_uri(uri: &str) -> Result<Self> { - let data = DATA_URI_RE - .captures(uri) - .ok_or(DichroismError::UriDataExtract)? - .name("data") - .ok_or(DichroismError::UriDataExtract)? - .as_str(); - let original = image::load_from_memory(&decode(data)?)?; - let fullsize = original.resize(PHOTO_FULLSIZE_XY, PHOTO_FULLSIZE_XY, FilterType::Lanczos3); - let base = original.resize(PHOTO_BASE_XY, PHOTO_BASE_XY, FilterType::Lanczos3); - - let (width, height) = original.dimensions(); - let thumbnail = if width > height { - let offset = (width - height) / 2; - original.crop_imm(offset, 0, width - offset * 2, height) - } else { - let offset = (height - width) / 2; - original.crop_imm(0, offset, width, height - offset * 2) - } - .resize(PHOTO_THUMBNAIL_XY, PHOTO_THUMBNAIL_XY, FilterType::Lanczos3); - - Ok(Self { - original: Photo::from_image(&original)?, - fullsize: Photo::from_image(&fullsize)?, - base: Photo::from_image(&base)?, - thumbnail: Photo::from_image(&thumbnail)?, - }) - } -} diff --git a/dichroism/src/models/product.rs b/dichroism/src/models/product.rs index be0a5ec..4a3d782 100644 --- a/dichroism/src/models/product.rs +++ b/dichroism/src/models/product.rs @@ -1,11 +1,12 @@ use super::PhotoSet; +#[derive(Debug, Clone)] pub struct Product { - pub id: u32, + pub id: Option<i32>, pub name: String, pub description: String, - pub cents: u32, - pub quantity: u32, + pub cents: i32, + pub quantity: i32, pub featured: bool, pub photo_set: PhotoSet, pub category: String, diff --git a/dichroism/src/repo/entities/mod.rs b/dichroism/src/repo/entities/mod.rs index 2cff899..288f1e3 100644 --- a/dichroism/src/repo/entities/mod.rs +++ b/dichroism/src/repo/entities/mod.rs @@ -1,36 +1,9 @@ -use crate::models; -use crate::schema::products; +mod photo_set; +mod photo_set_form; +mod product; +mod product_form; -#[derive(Debug, Clone, Identifiable, Queryable, Serialize)] -pub struct Product { - pub id: i32, - pub name: String, - pub description: String, - pub quantity: i32, - pub cents: i32, - pub featured: i32, - pub original: String, - pub fullsize: String, - pub base: String, - pub thumbnail: String, -} - -impl Into<models::Product> for Product { - fn into(self) -> models::Product { - models::Product { - id: self.id as u32, - name: self.name, - description: self.description, - quantity: self.quantity as u32, - cents: self.cents as u32, - featured: self.featured != 0, // TODO: is this safe? - category: String::new(), // TODO: real category - photo_set: models::PhotoSet::from_photos( - models::Photo::from_filename(self.original), - models::Photo::from_filename(self.fullsize), - models::Photo::from_filename(self.base), - models::Photo::from_filename(self.thumbnail), - ), - } - } -} +pub use photo_set::*; +pub use photo_set_form::*; +pub use product::*; +pub use product_form::*; diff --git a/dichroism/src/repo/entities/photo_set.rs b/dichroism/src/repo/entities/photo_set.rs new file mode 100644 index 0000000..6e48e12 --- /dev/null +++ b/dichroism/src/repo/entities/photo_set.rs @@ -0,0 +1,22 @@ +use crate::models; + +#[derive(Debug, Clone, Queryable)] +pub struct PhotoSet { + pub id: i32, + pub original: String, + pub fullsize: String, + pub base: String, + pub thumbnail: String, +} + +impl Into<models::PhotoSet> for PhotoSet { + fn into(self) -> models::PhotoSet { + models::PhotoSet { + id: Some(self.id), + original: models::Photo::new(self.original), + fullsize: models::Photo::new(self.fullsize), + base: models::Photo::new(self.base), + thumbnail: models::Photo::new(self.thumbnail), + } + } +} diff --git a/dichroism/src/repo/entities/photo_set_form.rs b/dichroism/src/repo/entities/photo_set_form.rs new file mode 100644 index 0000000..611c8f0 --- /dev/null +++ b/dichroism/src/repo/entities/photo_set_form.rs @@ -0,0 +1,24 @@ +use crate::models::PhotoSet; +use crate::schema::photo_sets; + +#[derive(Insertable, AsChangeset)] +#[table_name = "photo_sets"] +pub struct PhotoSetForm { + pub id: i32, + pub original: String, + pub fullsize: String, + pub base: String, + pub thumbnail: String, +} + +impl From<PhotoSet> for PhotoSetForm { + fn from(p: PhotoSet) -> Self { + Self { + id: p.id.unwrap_or(-1), + original: p.original.id, + fullsize: p.fullsize.id, + base: p.base.id, + thumbnail: p.thumbnail.id, + } + } +} diff --git a/dichroism/src/repo/entities/product.rs b/dichroism/src/repo/entities/product.rs new file mode 100644 index 0000000..e6ba223 --- /dev/null +++ b/dichroism/src/repo/entities/product.rs @@ -0,0 +1,38 @@ +use crate::models; +use crate::schema::products; + +#[derive(Debug, Clone, Identifiable, Queryable)] +pub struct Product { + pub id: i32, + pub name: String, + pub description: String, + pub quantity: i32, + pub cents: i32, + pub featured: i32, + pub photo_set_id: i32, + pub original: String, + pub fullsize: String, + pub base: String, + pub thumbnail: String, +} + +impl Into<models::Product> for Product { + fn into(self) -> models::Product { + models::Product { + id: Some(self.id), + name: self.name, + description: self.description, + quantity: self.quantity, + cents: self.cents, + featured: self.featured != 0, // TODO: is this safe? + category: String::new(), // TODO: real category + photo_set: models::PhotoSet { + id: Some(self.photo_set_id), + original: models::Photo::new(self.original), + fullsize: models::Photo::new(self.fullsize), + base: models::Photo::new(self.base), + thumbnail: models::Photo::new(self.thumbnail), + }, + } + } +} diff --git a/dichroism/src/repo/entities/product_form.rs b/dichroism/src/repo/entities/product_form.rs new file mode 100644 index 0000000..0d6e452 --- /dev/null +++ b/dichroism/src/repo/entities/product_form.rs @@ -0,0 +1,28 @@ +use crate::models::*; +use crate::schema::products; + +#[derive(Insertable, AsChangeset)] +#[table_name = "products"] +pub struct ProductForm { + pub id: i32, + pub name: String, + pub description: String, + pub quantity: i32, + pub cents: i32, + pub featured: i32, + pub photo_set: i32, +} + +impl From<Product> for ProductForm { + fn from(p: Product) -> Self { + Self { + id: p.id.unwrap_or(-1), + name: p.name, + description: p.description, + quantity: p.quantity, + cents: p.cents, + featured: p.featured as i32, + photo_set: p.photo_set.id.unwrap_or(-1), // TODO: ? + } + } +} diff --git a/dichroism/src/repo/mod.rs b/dichroism/src/repo/mod.rs index 0df8aad..6dc245a 100644 --- a/dichroism/src/repo/mod.rs +++ b/dichroism/src/repo/mod.rs @@ -1,13 +1,53 @@ use crate::models; -use crate::schema::*; +use diesel::insert_into; use diesel::prelude::*; use diesel::result::Error; +use diesel::update; +use entities::*; -mod entities; +pub mod entities; type DBConn = SqliteConnection; -pub fn read_products(conn: &DBConn) -> Result<Vec<models::Product>, Error> { +pub fn store_photo_set( + conn: &DBConn, + mut photo_set: models::PhotoSet, +) -> Result<models::PhotoSet, Error> { + use crate::schema::photo_sets::dsl::*; + if photo_set.id.is_some() { + // update + let form = PhotoSetForm::from(photo_set.clone()); + update(photo_sets).set(&form).execute(conn)?; + } else { + // insert + photo_set.id = Some(find_next_photo_set_id(conn)?); + let form = PhotoSetForm::from(photo_set.clone()); + insert_into(photo_sets).values(&form).execute(conn)?; + } + Ok(photo_set) +} + +pub fn store_product( + conn: &DBConn, + mut product: models::Product, +) -> Result<models::Product, Error> { + use crate::schema::products::dsl::*; + if product.id.is_some() { + // update + let form = ProductForm::from(product.clone()); + update(products).set(&form).execute(conn)?; + } else { + // insert + product.id = Some(find_next_product_id(conn)?); + let form = ProductForm::from(product.clone()); + insert_into(products).values(&form).execute(conn)?; + } + + Ok(product) +} + +pub fn find_all(conn: &DBConn) -> Result<Vec<models::Product>, Error> { + use crate::schema::*; let query = products::table.inner_join(photo_sets::table).select(( products::id, products::name, @@ -15,6 +55,7 @@ pub fn read_products(conn: &DBConn) -> Result<Vec<models::Product>, Error> { products::quantity, products::cents, products::featured, + photo_sets::id, photo_sets::original, photo_sets::fullsize, photo_sets::base, @@ -26,3 +67,44 @@ pub fn read_products(conn: &DBConn) -> Result<Vec<models::Product>, Error> { .map(|p| p.into()) .collect::<Vec<models::Product>>()) } + +pub fn find(conn: &DBConn, id: i32) -> Result<Option<models::Product>, Error> { + use crate::schema::*; + let query = products::table + .inner_join(photo_sets::table) + .filter(products::id.eq(id)) + .select(( + products::id, + products::name, + products::description, + products::quantity, + products::cents, + products::featured, + photo_sets::id, + photo_sets::original, + photo_sets::fullsize, + photo_sets::base, + photo_sets::thumbnail, + )); + let product = query.first::<Product>(conn).map(|p| p.into()); + match product { + Ok(p) => Ok(Some(p)), + Err(e) => { + if e == Error::NotFound { + Ok(None) + } else { + Err(e) + } + } + } +} + +fn find_next_product_id(conn: &DBConn) -> Result<i32, Error> { + use crate::schema::products::dsl::*; + Ok(products.select(id).order(id.desc()).first::<i32>(conn)? + 1) +} + +fn find_next_photo_set_id(conn: &DBConn) -> Result<i32, Error> { + use crate::schema::photo_sets::dsl::*; + Ok(photo_sets.select(id).order(id.desc()).first::<i32>(conn)? + 1) +} |