summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam T. Carpenter <atc@53hor.net>2020-11-02 20:36:18 -0500
committerAdam T. Carpenter <atc@53hor.net>2020-11-02 20:36:18 -0500
commitdcc96d0b349583e5d6a0f25ae1f7a3ffa3769788 (patch)
tree98a9cc9d322e309c05db400f4c536164bee4daac
parent9480317011b57d3be7b903048f4a85d02979c7c7 (diff)
downloadtheglassyladies-dcc96d0b349583e5d6a0f25ae1f7a3ffa3769788.tar.xz
theglassyladies-dcc96d0b349583e5d6a0f25ae1f7a3ffa3769788.zip
swapped json payload url encoded images for multipart form data
-rw-r--r--dichroism/Cargo.lock49
-rw-r--r--dichroism/Cargo.toml3
-rw-r--r--dichroism/src/dtos/mod.rs2
-rw-r--r--dichroism/src/dtos/photo_set_get.rs22
-rw-r--r--dichroism/src/dtos/product_patch.rs22
-rw-r--r--dichroism/src/dtos/product_post.rs2
-rw-r--r--dichroism/src/handlers.rs99
-rw-r--r--dichroism/src/image_service.rs18
-rw-r--r--dichroism/src/repo/mod.rs109
-rw-r--r--dichroism/src/repo/photo_set_repo.rs42
-rw-r--r--dichroism/src/repo/product_repo.rs78
11 files changed, 284 insertions, 162 deletions
diff --git a/dichroism/Cargo.lock b/dichroism/Cargo.lock
index 9184f40..06bae04 100644
--- a/dichroism/Cargo.lock
+++ b/dichroism/Cargo.lock
@@ -93,6 +93,24 @@ dependencies = [
]
[[package]]
+name = "actix-multipart"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "774bfeb11b54bf9c857a005b8ab893293da4eaff79261a66a9200dab7f5ab6e3"
+dependencies = [
+ "actix-service",
+ "actix-utils",
+ "actix-web",
+ "bytes",
+ "derive_more",
+ "futures-util",
+ "httparse",
+ "log",
+ "mime",
+ "twoway",
+]
+
+[[package]]
name = "actix-router"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -720,14 +738,17 @@ dependencies = [
name = "dichroism"
version = "0.1.0"
dependencies = [
+ "actix-multipart",
"actix-web",
"async-std",
"base64 0.13.0",
"diesel",
"env_logger",
+ "futures",
"image",
"listenfd",
"log",
+ "mime",
"once_cell",
"regex",
"serde",
@@ -877,6 +898,7 @@ checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613"
dependencies = [
"futures-channel",
"futures-core",
+ "futures-executor",
"futures-io",
"futures-sink",
"futures-task",
@@ -900,6 +922,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
[[package]]
+name = "futures-executor"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
name = "futures-io"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2191,12 +2224,28 @@ dependencies = [
]
[[package]]
+name = "twoway"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc"
+dependencies = [
+ "memchr",
+ "unchecked-index",
+]
+
+[[package]]
name = "typenum"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
+name = "unchecked-index"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
+
+[[package]]
name = "unicode-bidi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/dichroism/Cargo.toml b/dichroism/Cargo.toml
index f699617..7dd6036 100644
--- a/dichroism/Cargo.toml
+++ b/dichroism/Cargo.toml
@@ -6,6 +6,9 @@ edition = "2018"
[dependencies]
actix-web = "3"
+actix-multipart = "0.3"
+mime = "0.3"
+futures = "0.3"
async-std = "1"
base64 = "0.13"
diesel = { version = "1", features = [ "sqlite", "r2d2" ] }
diff --git a/dichroism/src/dtos/mod.rs b/dichroism/src/dtos/mod.rs
index cc8edd1..a3e27e5 100644
--- a/dichroism/src/dtos/mod.rs
+++ b/dichroism/src/dtos/mod.rs
@@ -1,7 +1,9 @@
+mod photo_set_get;
mod product_get;
mod product_patch;
mod product_post;
+pub use photo_set_get::*;
pub use product_get::*;
pub use product_patch::*;
pub use product_post::*;
diff --git a/dichroism/src/dtos/photo_set_get.rs b/dichroism/src/dtos/photo_set_get.rs
new file mode 100644
index 0000000..0736617
--- /dev/null
+++ b/dichroism/src/dtos/photo_set_get.rs
@@ -0,0 +1,22 @@
+use crate::models::PhotoSet;
+
+#[derive(Debug, Serialize)]
+pub struct PhotoSetGet {
+ pub id: i32,
+ pub original: String,
+ pub fullsize: String,
+ pub base: String,
+ pub thumbnail: String,
+}
+
+impl From<PhotoSet> for PhotoSetGet {
+ 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/dtos/product_patch.rs b/dichroism/src/dtos/product_patch.rs
index ed8bf1a..f231469 100644
--- a/dichroism/src/dtos/product_patch.rs
+++ b/dichroism/src/dtos/product_patch.rs
@@ -1,3 +1,5 @@
+use crate::models::Product;
+
#[derive(Debug, Deserialize)]
pub struct ProductPatch {
pub id: i32,
@@ -7,5 +9,23 @@ pub struct ProductPatch {
pub description: Option<String>,
pub featured: Option<bool>,
pub category_path: Option<String>,
- pub photo_data: Option<String>,
+ pub photo_set: Option<i32>,
+}
+
+impl ProductPatch {
+ pub fn patch(self, product: &mut Product) {
+ if let Some(name) = self.name {
+ product.name = name;
+ }
+ if let Some(category) = self.category_path {
+ product.category = category;
+ }
+ if let Some(description) = self.description {
+ product.description = description;
+ }
+
+ product.quantity = self.quantity.unwrap_or(product.quantity);
+ product.cents = self.cents.unwrap_or(product.cents);
+ product.featured = self.featured.unwrap_or(product.featured);
+ }
}
diff --git a/dichroism/src/dtos/product_post.rs b/dichroism/src/dtos/product_post.rs
index ef07536..ee3ba03 100644
--- a/dichroism/src/dtos/product_post.rs
+++ b/dichroism/src/dtos/product_post.rs
@@ -6,5 +6,5 @@ pub struct ProductPost {
pub description: String,
pub featured: bool,
pub category_path: String,
- pub photo_data: String,
+ pub photo_set: i32,
}
diff --git a/dichroism/src/handlers.rs b/dichroism/src/handlers.rs
index 5d678f5..43c11d2 100644
--- a/dichroism/src/handlers.rs
+++ b/dichroism/src/handlers.rs
@@ -1,9 +1,11 @@
use crate::dtos::*;
use crate::image_service;
use crate::models::Product;
-use crate::repo;
+use crate::repo::{photo_set_repo, product_repo};
use crate::types::DbPool;
+use actix_multipart::Multipart;
use actix_web::{get, patch, post, web, Error, HttpResponse, Responder};
+use futures::{StreamExt, TryStreamExt};
fn to_internal_error(e: impl std::error::Error) -> HttpResponse {
eprintln!("{}", e);
@@ -18,7 +20,7 @@ async fn hello() -> impl Responder {
#[get("/products")]
async fn get_products(pool: web::Data<DbPool>) -> Result<HttpResponse, Error> {
let conn = pool.get().map_err(to_internal_error)?;
- let products: Vec<ProductGet> = web::block(move || repo::find_all(&conn))
+ let products: Vec<ProductGet> = web::block(move || product_repo::find_all(&conn))
.await
.map_err(to_internal_error)?
.into_iter()
@@ -30,43 +32,33 @@ async fn get_products(pool: web::Data<DbPool>) -> Result<HttpResponse, Error> {
#[patch("/products")]
async fn patch_product(
pool: web::Data<DbPool>,
- patch: web::Json<ProductPatch>,
+ web::Json(patch): web::Json<ProductPatch>,
) -> Result<HttpResponse, Error> {
- let patch = patch.into_inner();
let id = patch.id;
+ // get product referenced by patch
let conn = pool.get().map_err(to_internal_error)?;
- let mut product = web::block(move || repo::find(&conn, id))
+ let mut product = web::block(move || product_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 = web::block(move || image_service::generate_photo_set(&data_uri))
- .await
- .map_err(|e| {
- eprintln!("{}", e.to_string());
- HttpResponse::InternalServerError().body(e.to_string())
- })?;
+ // get photo set referenced by patch
+ if let Some(id) = patch.photo_set {
let conn = pool.get().map_err(to_internal_error)?;
- let photo_set = web::block(move || repo::store_photo_set(&conn, photo_set))
+ let photo_set = web::block(move || photo_set_repo::find(&conn, id))
.await
- .map_err(to_internal_error)?;
+ .map_err(to_internal_error)?
+ .ok_or_else(|| HttpResponse::NotFound().body("Photo set not found"))?;
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);
+ // update product fields
+ patch.patch(&mut product);
// store the updated product
let conn = pool.get().map_err(to_internal_error)?;
- let product = web::block(move || repo::store_product(&conn, product))
+ let product = web::block(move || product_repo::store(&conn, product))
.await
.map_err(to_internal_error)?;
@@ -76,24 +68,17 @@ async fn patch_product(
#[post("/products")]
async fn post_product(
pool: web::Data<DbPool>,
- post: web::Json<ProductPost>,
+ web::Json(post): web::Json<ProductPost>,
) -> Result<HttpResponse, Error> {
- let post = post.into_inner();
- dbg!(&post);
-
- // create new photo_set
- let data_uri = post.photo_data;
- let photo_set = web::block(move || image_service::generate_photo_set(&data_uri))
- .await
- .map_err(|e| {
- eprintln!("{}", e.to_string());
- HttpResponse::InternalServerError().body(e.to_string())
- })?;
+ // find associated photo set
+ let photo_set = post.photo_set;
let conn = pool.get().map_err(to_internal_error)?;
- let photo_set = web::block(move || repo::store_photo_set(&conn, photo_set))
+ let photo_set = web::block(move || photo_set_repo::find(&conn, photo_set))
.await
- .map_err(to_internal_error)?;
+ .map_err(to_internal_error)?
+ .ok_or_else(|| HttpResponse::NotFound().body("Photo set not found."))?;
+ // create new product
let product = Product {
id: None,
name: post.name,
@@ -105,10 +90,48 @@ async fn post_product(
photo_set,
};
+ // store product
let conn = pool.get().map_err(to_internal_error)?;
- let product = web::block(move || repo::store_product(&conn, product))
+ let product = web::block(move || product_repo::store(&conn, product))
.await
.map_err(to_internal_error)?;
Ok(HttpResponse::Ok().json::<ProductGet>(product.into()))
}
+
+#[post("/photos")]
+async fn post_photo(
+ pool: web::Data<DbPool>,
+ mut payload: Multipart,
+) -> Result<HttpResponse, Error> {
+ let mut responses: Vec<PhotoSetGet> = Vec::new();
+
+ if let Ok(Some(mut field)) = payload.try_next().await {
+ // bail if a non-JPEG file was going to be uploaded
+ if field.content_type() != &mime::IMAGE_JPEG {
+ return Ok(HttpResponse::BadRequest().body("File must be a JPEG image."));
+ }
+
+ // grab all bytes
+ let mut data: Vec<u8> = Vec::new();
+ while let Some(chunk) = field.next().await {
+ let chunk = chunk?;
+ data.extend(chunk);
+ }
+
+ // create new photo_set
+ let photo_set = web::block(move || image_service::generate_photo_set(&data))
+ .await
+ .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 || photo_set_repo::store(&conn, photo_set))
+ .await
+ .map_err(to_internal_error)?;
+ responses.push(photo_set.into());
+ }
+
+ Ok(HttpResponse::Ok().json(responses))
+}
diff --git a/dichroism/src/image_service.rs b/dichroism/src/image_service.rs
index 3a31e04..a69cca2 100644
--- a/dichroism/src/image_service.rs
+++ b/dichroism/src/image_service.rs
@@ -2,28 +2,14 @@ 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 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.")
-});
-
-pub fn generate_photo_set(uri: &str) -> Result<PhotoSet, DichroismError> {
- let data = DATA_URI_RE
- .captures(uri)
- .ok_or_else(|| DichroismError::UriDataExtract("Failed to parse URI".to_string()))?
- .name("data")
- .ok_or_else(|| DichroismError::UriDataExtract("Failed to extract data".to_string()))?
- .as_str();
- let original = image::load_from_memory(&decode(data)?)?;
+pub fn generate_photo_set(data: &[u8]) -> Result<PhotoSet, DichroismError> {
+ let original = image::load_from_memory(&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);
diff --git a/dichroism/src/repo/mod.rs b/dichroism/src/repo/mod.rs
index 6dc245a..75a063c 100644
--- a/dichroism/src/repo/mod.rs
+++ b/dichroism/src/repo/mod.rs
@@ -1,110 +1,7 @@
-use crate::models;
-use diesel::insert_into;
-use diesel::prelude::*;
-use diesel::result::Error;
-use diesel::update;
-use entities::*;
+use diesel::SqliteConnection;
pub mod entities;
+pub mod photo_set_repo;
+pub mod product_repo;
type DBConn = SqliteConnection;
-
-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,
- products::description,
- products::quantity,
- products::cents,
- products::featured,
- photo_sets::id,
- photo_sets::original,
- photo_sets::fullsize,
- photo_sets::base,
- photo_sets::thumbnail,
- ));
- Ok(query
- .load::<entities::Product>(conn)?
- .into_iter()
- .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)
-}
diff --git a/dichroism/src/repo/photo_set_repo.rs b/dichroism/src/repo/photo_set_repo.rs
new file mode 100644
index 0000000..339ea91
--- /dev/null
+++ b/dichroism/src/repo/photo_set_repo.rs
@@ -0,0 +1,42 @@
+use super::entities::*;
+use super::DBConn;
+use crate::models;
+use diesel::{insert_into, prelude::*, result::Error, update};
+
+pub fn store(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_id(conn)?);
+ let form = PhotoSetForm::from(photo_set.clone());
+ insert_into(photo_sets).values(&form).execute(conn)?;
+ }
+ Ok(photo_set)
+}
+
+pub fn find(conn: &DBConn, dbid: i32) -> Result<Option<models::PhotoSet>, Error> {
+ use crate::schema::photo_sets::dsl::*;
+ let query = photo_sets
+ .filter(id.eq(dbid))
+ .select((id, original, fullsize, base, thumbnail));
+ let photo_set = query.first::<PhotoSet>(conn).map(|p| p.into());
+ match photo_set {
+ Ok(p) => Ok(Some(p)),
+ Err(e) => {
+ if e == Error::NotFound {
+ Ok(None)
+ } else {
+ Err(e)
+ }
+ }
+ }
+}
+
+fn find_next_id(conn: &DBConn) -> Result<i32, Error> {
+ use crate::schema::photo_sets::dsl::*;
+ Ok(photo_sets.select(id).order(id.desc()).first::<i32>(conn)? + 1)
+}
diff --git a/dichroism/src/repo/product_repo.rs b/dichroism/src/repo/product_repo.rs
new file mode 100644
index 0000000..7b3aaac
--- /dev/null
+++ b/dichroism/src/repo/product_repo.rs
@@ -0,0 +1,78 @@
+use super::entities::*;
+use super::DBConn;
+use crate::models;
+use diesel::{insert_into, prelude::*, result::Error, update};
+
+pub fn store(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_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,
+ products::description,
+ products::quantity,
+ products::cents,
+ products::featured,
+ photo_sets::id,
+ photo_sets::original,
+ photo_sets::fullsize,
+ photo_sets::base,
+ photo_sets::thumbnail,
+ ));
+ Ok(query
+ .load::<Product>(conn)?
+ .into_iter()
+ .map(|p| p.into())
+ .collect::<Vec<models::Product>>())
+}
+
+pub fn find(conn: &DBConn, dbid: i32) -> Result<Option<models::Product>, Error> {
+ use crate::schema::*;
+ let query = products::table
+ .inner_join(photo_sets::table)
+ .filter(products::id.eq(dbid))
+ .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_id(conn: &DBConn) -> Result<i32, Error> {
+ use crate::schema::products::dsl::*;
+ Ok(products.select(id).order(id.desc()).first::<i32>(conn)? + 1)
+}