summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam T. Carpenter <atc@53hor.net>2020-10-31 10:14:31 -0400
committerAdam T. Carpenter <atc@53hor.net>2020-10-31 10:14:31 -0400
commit3e1eadbbfdca1b2c0cb32ba4c8e1160a60e0ccb8 (patch)
tree3b712bc31a6db0a16ddfd4a02529d8b2497a5c54
parent00c71f6baff136a88805ca2e3f9ef72453ac9f35 (diff)
downloadtheglassyladies-3e1eadbbfdca1b2c0cb32ba4c8e1160a60e0ccb8.tar.xz
theglassyladies-3e1eadbbfdca1b2c0cb32ba4c8e1160a60e0ccb8.zip
All basic functionality implemented.
-rw-r--r--dichroism/src/dtos/mod.rs75
-rw-r--r--dichroism/src/dtos/product_get.rs32
-rw-r--r--dichroism/src/dtos/product_patch.rs11
-rw-r--r--dichroism/src/dtos/product_post.rs10
-rw-r--r--dichroism/src/handlers.rs101
-rw-r--r--dichroism/src/image_service.rs66
-rw-r--r--dichroism/src/main.rs5
-rw-r--r--dichroism/src/models/dbid.rs4
-rw-r--r--dichroism/src/models/photo.rs32
-rw-r--r--dichroism/src/models/photo_set.rs57
-rw-r--r--dichroism/src/models/product.rs7
-rw-r--r--dichroism/src/repo/entities/mod.rs43
-rw-r--r--dichroism/src/repo/entities/photo_set.rs22
-rw-r--r--dichroism/src/repo/entities/photo_set_form.rs24
-rw-r--r--dichroism/src/repo/entities/product.rs38
-rw-r--r--dichroism/src/repo/entities/product_form.rs28
-rw-r--r--dichroism/src/repo/mod.rs88
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)
+}