summaryrefslogtreecommitdiff
path: root/dichroism
diff options
context:
space:
mode:
Diffstat (limited to 'dichroism')
-rw-r--r--dichroism/migrations/2020-10-21-221149_products/down.sql1
-rw-r--r--dichroism/migrations/2020-10-21-221149_products/up.sql11
-rw-r--r--dichroism/src/config.rs8
-rw-r--r--dichroism/src/constants.rs3
-rw-r--r--dichroism/src/dtos/mod.rs70
-rw-r--r--dichroism/src/entities/mod.rs7
-rw-r--r--dichroism/src/entities/photo.rs5
-rw-r--r--dichroism/src/entities/photo_set.rs8
-rw-r--r--dichroism/src/entities/product.rs11
-rw-r--r--dichroism/src/handlers.rs40
-rw-r--r--dichroism/src/main.rs18
-rw-r--r--dichroism/src/models/photo.rs24
-rw-r--r--dichroism/src/models/photo_set.rs72
-rw-r--r--dichroism/src/models/product.rs26
-rw-r--r--dichroism/src/photo_repo.rs41
-rw-r--r--dichroism/src/product_repo.rs19
-rw-r--r--dichroism/src/schema.rs15
17 files changed, 260 insertions, 119 deletions
diff --git a/dichroism/migrations/2020-10-21-221149_products/down.sql b/dichroism/migrations/2020-10-21-221149_products/down.sql
new file mode 100644
index 0000000..f800c5c
--- /dev/null
+++ b/dichroism/migrations/2020-10-21-221149_products/down.sql
@@ -0,0 +1 @@
+DROP TABLE "products"
diff --git a/dichroism/migrations/2020-10-21-221149_products/up.sql b/dichroism/migrations/2020-10-21-221149_products/up.sql
new file mode 100644
index 0000000..9e9c696
--- /dev/null
+++ b/dichroism/migrations/2020-10-21-221149_products/up.sql
@@ -0,0 +1,11 @@
+CREATE TABLE "products" (
+ "id" INTEGER NOT NULL,
+ "photo_set" INTEGER NOT NULL,
+ "cents" INTEGER NOT NULL,
+ "quantity" INTEGER NOT NULL,
+ "name" TEXT NOT NULL,
+ "description" TEXT NOT NULL,
+ "featured" INTEGER NOT NULL,
+ PRIMARY KEY("id"),
+ FOREIGN KEY("photo_set") REFERENCES "photo_sets"("id")
+)
diff --git a/dichroism/src/config.rs b/dichroism/src/config.rs
index c1c42f0..0dbc10e 100644
--- a/dichroism/src/config.rs
+++ b/dichroism/src/config.rs
@@ -1,5 +1,6 @@
use crate::constants::DEFAULT_CONFIG;
use crate::result::Result;
+use once_cell::sync::Lazy;
use serde::Deserialize;
use std::env::var;
use std::fs::File;
@@ -7,6 +8,13 @@ use std::io::prelude::*;
use std::net::SocketAddr;
use toml::from_str;
+pub static CONFIG_INSTANCE: Lazy<Config> = Lazy::new(|| {
+ Config::from_toml().unwrap_or_else(|e| {
+ eprintln!("Error: {}", e.to_string());
+ std::process::exit(1);
+ })
+});
+
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
pub db_url: String,
diff --git a/dichroism/src/constants.rs b/dichroism/src/constants.rs
index 87d0442..72a8de9 100644
--- a/dichroism/src/constants.rs
+++ b/dichroism/src/constants.rs
@@ -1 +1,4 @@
pub const DEFAULT_CONFIG: &str = "./Dichroism.toml";
+pub const PHOTO_FULLSIZE_XY: u32 = 1000;
+pub const PHOTO_BASE_XY: u32 = 640;
+pub const PHOTO_THUMBNAIL_XY: u32 = 300;
diff --git a/dichroism/src/dtos/mod.rs b/dichroism/src/dtos/mod.rs
new file mode 100644
index 0000000..76b04c7
--- /dev/null
+++ b/dichroism/src/dtos/mod.rs
@@ -0,0 +1,70 @@
+use crate::models;
+use crate::result::Result;
+
+#[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.path,
+ photo_base: p.photo_set.base.path,
+ photo_thumbnail: p.photo_set.thumbnail.path,
+ }
+ }
+}
diff --git a/dichroism/src/entities/mod.rs b/dichroism/src/entities/mod.rs
new file mode 100644
index 0000000..0b17cf7
--- /dev/null
+++ b/dichroism/src/entities/mod.rs
@@ -0,0 +1,7 @@
+mod photo;
+mod photo_set;
+mod product;
+
+pub use photo::Photo;
+pub use photo_set::PhotoSet;
+pub use product::Product;
diff --git a/dichroism/src/entities/photo.rs b/dichroism/src/entities/photo.rs
new file mode 100644
index 0000000..34e9c28
--- /dev/null
+++ b/dichroism/src/entities/photo.rs
@@ -0,0 +1,5 @@
+#[derive(Debug, Clone, Serialize)]
+pub struct Photo {
+ pub id: i32,
+ pub path: String,
+}
diff --git a/dichroism/src/entities/photo_set.rs b/dichroism/src/entities/photo_set.rs
new file mode 100644
index 0000000..b9f7710
--- /dev/null
+++ b/dichroism/src/entities/photo_set.rs
@@ -0,0 +1,8 @@
+#[derive(Debug, Clone, Serialize)]
+pub struct PhotoSet {
+ pub id: i32,
+ pub base: i32,
+ pub fullsize: i32,
+ pub thumbnail: i32,
+ pub original: i32,
+}
diff --git a/dichroism/src/entities/product.rs b/dichroism/src/entities/product.rs
new file mode 100644
index 0000000..4f47c1d
--- /dev/null
+++ b/dichroism/src/entities/product.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, Clone, Queryable, Serialize)]
+pub struct Product {
+ pub id: i32,
+ pub photo_set: i32,
+ pub cents: i32,
+ pub quantity: i32,
+ pub name: String,
+ pub description: String,
+ pub featured: i32,
+}
+
diff --git a/dichroism/src/handlers.rs b/dichroism/src/handlers.rs
index 1f657c2..beecb72 100644
--- a/dichroism/src/handlers.rs
+++ b/dichroism/src/handlers.rs
@@ -1,6 +1,6 @@
-//use super::product_repo;
-use super::types::DbPool;
-//use crate::config::Config;
+use crate::dtos::*;
+use crate::product_repo;
+use crate::types::DbPool;
use actix_web::{get, patch, post, web, Error, HttpResponse, Responder};
#[get("/")]
@@ -9,24 +9,24 @@ async fn hello() -> impl Responder {
}
#[get("/products")]
-async fn get_products(_pool: web::Data<DbPool>) -> Result<HttpResponse, Error> {
- dbg!("got products");
- Ok(HttpResponse::Ok().finish())
- //let conn = pool.get().expect("Couldn't get DB connection from pool.");
- //let products = web::block(move || product_repo::read_products(&conn))
- // .await
- // .map_err(|e| {
- // eprintln!("{}", e);
- // HttpResponse::InternalServerError().finish()
- // })?;
+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 = web::block(move || product_repo::read_products(&conn))
+ .await
+ .map_err(|e| {
+ eprintln!("{}", e);
+ HttpResponse::InternalServerError().finish()
+ })?;
+ //dbg!(&products);
//Ok(HttpResponse::Ok().json(products))
+ todo!()
}
#[patch("/products/{id}")]
async fn update_product(
_pool: web::Data<DbPool>,
id: web::Path<u32>,
- updated_product: web::Json<UpdatedProduct>,
+ updated_product: web::Json<ProductPatch>,
) -> Result<HttpResponse, Error> {
dbg!(id, updated_product);
Ok(HttpResponse::Ok().finish())
@@ -40,15 +40,3 @@ async fn create_product(
dbg!(new_product);
Ok(HttpResponse::Ok().finish())
}
-
-#[derive(Debug, Deserialize)]
-pub struct NewProduct {
- pub name: String,
- pub description: String,
-}
-
-#[derive(Debug, Deserialize)]
-pub struct UpdatedProduct {
- pub name: Option<String>,
- pub description: Option<String>,
-}
diff --git a/dichroism/src/main.rs b/dichroism/src/main.rs
index fb5e2c8..5ecd9a4 100644
--- a/dichroism/src/main.rs
+++ b/dichroism/src/main.rs
@@ -4,7 +4,7 @@ extern crate serde;
extern crate diesel;
use actix_web::{App, HttpServer};
-use config::Config;
+use config::CONFIG_INSTANCE as CONFIG;
use diesel::prelude::SqliteConnection;
use diesel::r2d2::ConnectionManager;
use diesel::r2d2::Pool;
@@ -13,10 +13,11 @@ use result::Result;
mod config;
mod constants;
+mod dtos;
+mod entities;
mod error;
mod handlers;
mod models;
-mod photo_repo;
mod product_repo;
mod result;
mod schema;
@@ -24,18 +25,13 @@ mod types;
#[actix_web::main]
async fn main() -> Result<()> {
- // Gather config.
- let config = Config::from_toml()?;
- let bind_addr = config.bind_addr;
-
// Initialize DB connection pool.
- let manager = ConnectionManager::<SqliteConnection>::new(&config.db_url);
+ let manager = ConnectionManager::<SqliteConnection>::new(&CONFIG.db_url);
let pool = Pool::builder().build(manager)?;
// Initialize application server.
let mut server = HttpServer::new(move || {
App::new()
- .data(config.clone())
.data(pool.clone())
.service(handlers::hello)
.service(handlers::get_products)
@@ -43,16 +39,16 @@ async fn main() -> Result<()> {
.service(handlers::create_product)
});
+ // If using listenfd, bind to it instead of the configured address to allow for cargo watch
+ // auto-reloading
let mut listenfd = ListenFd::from_env();
server = if let Some(l) = listenfd
.take_tcp_listener(0)
.expect("Unable to grab TCP listener!")
{
- // If using listenfd, use it to allow for cargo watch auto-reloading.
server.listen(l)?
} else {
- // Bind to config for release.
- server.bind(bind_addr)?
+ server.bind(CONFIG.bind_addr)?
};
Ok(server.run().await?)
diff --git a/dichroism/src/models/photo.rs b/dichroism/src/models/photo.rs
index 2e8b3bf..dee6288 100644
--- a/dichroism/src/models/photo.rs
+++ b/dichroism/src/models/photo.rs
@@ -1,5 +1,27 @@
+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)]
pub struct Photo {
- pub id: i32,
pub path: String,
}
+
+impl Photo {
+ 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)?;
+
+ Ok(Self {
+ path: path.to_str().ok_or(DichroismError::ImageWrite)?.to_string(),
+ })
+ }
+}
diff --git a/dichroism/src/models/photo_set.rs b/dichroism/src/models/photo_set.rs
index 2f6c128..84fe0e1 100644
--- a/dichroism/src/models/photo_set.rs
+++ b/dichroism/src/models/photo_set.rs
@@ -1,8 +1,68 @@
-#[derive(Debug, Queryable, Clone)]
+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.")
+});
+
pub struct PhotoSet {
- pub id: i32,
- pub base: i32,
- pub fullsize: i32,
- pub thumbnail: i32,
- pub original: 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_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)?,
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ //use super::*;
+
+ const TEST_DATA_URI: &str = include_str!("../unit_test_data/img_data_uri.txt");
+ const TEST_DATA_BASE64: &str = include_str!("../unit_test_data/test_data_base64.txt");
+
+ #[test]
+ fn test_gen_product_images() {
+ // PhotoSet::from_data_uri(TEST_DATA_URI.trim())
+ // .unwrap()
+ // .commit(".")
+ // .unwrap();
+ }
}
diff --git a/dichroism/src/models/product.rs b/dichroism/src/models/product.rs
index 9b81b8a..be0a5ec 100644
--- a/dichroism/src/models/product.rs
+++ b/dichroism/src/models/product.rs
@@ -1,26 +1,12 @@
-#[derive(Debug, Clone, Queryable, Serialize)]
+use super::PhotoSet;
+
pub struct Product {
- pub id: i32,
+ pub id: u32,
pub name: String,
- pub quantity: i32,
- pub cents: i32,
pub description: String,
+ pub cents: u32,
+ pub quantity: u32,
pub featured: bool,
- pub category_path: String,
pub photo_set: PhotoSet,
-}
-
-#[derive(Debug, Clone, Serialize)]
-pub struct PhotoSet {
- pub id: i32,
- pub base: i32,
- pub fullsize: i32,
- pub thumbnail: i32,
- pub original: i32,
-}
-
-#[derive(Debug, Clone, Serialize)]
-pub struct Photo {
- pub id: i32,
- pub path: String,
+ pub category: String,
}
diff --git a/dichroism/src/photo_repo.rs b/dichroism/src/photo_repo.rs
deleted file mode 100644
index 64390c8..0000000
--- a/dichroism/src/photo_repo.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-use super::models::*;
-use diesel::prelude::*;
-use diesel::result::Error;
-
-type DBConn = SqliteConnection;
-
-pub fn read_photos(conn: &DBConn) -> Result<Vec<Photo>, Error> {
- use crate::schema::photos::dsl::*;
- let results = photos.load::<Photo>(conn)?;
- Ok(results)
-}
-
-fn read_photos_by_path(conn: &DBConn, path: &str) -> Result<Vec<Photo>, Error> {
- use crate::schema::photos::dsl::*;
- let results = photos.filter(path.eq(path)).load::<Photo>(conn)?;
- Ok(results)
-}
-
-pub fn read_photo_by_path(conn: &DBConn, path: &str) -> Result<Option<Photo>, Error> {
- use crate::schema::photos::dsl::*;
- let results = photos.filter(path.eq(path)).limit(1).load::<Photo>(conn)?;
- Ok(results.first().cloned())
-}
-
-pub fn read_photo_by_id(conn: &DBConn, id: i32) -> Result<Option<Photo>, Error> {
- use crate::schema::photos::dsl::*;
- let results = photos.filter(id.eq(id)).limit(1).load::<Photo>(conn)?;
- Ok(results.first().cloned())
-}
-
-pub fn create_photo(conn: &DBConn, new_photo: NewPhoto) -> Result<Option<Photo>, Error> {
- use super::schema::photos;
- diesel::insert_into(photos::table)
- .values(&new_photo)
- .execute(conn)?;
- read_photo_by_path(conn, &new_photo.path)
-}
-
-pub fn update_photo() {
- todo!()
-}
diff --git a/dichroism/src/product_repo.rs b/dichroism/src/product_repo.rs
index 852b930..93176b2 100644
--- a/dichroism/src/product_repo.rs
+++ b/dichroism/src/product_repo.rs
@@ -1,21 +1,12 @@
-use super::models::Product;
+use crate::entities;
+use crate::models;
use diesel::prelude::*;
use diesel::result::Error;
type DBConn = SqliteConnection;
-pub fn create_product(conn: &DBConn) -> Result<Product, Error> {
- todo!()
-}
-
-pub fn read_products(_conn: &DBConn) -> Result<Vec<Product>, Error> {
- todo!()
-}
-
-pub fn update_product(conn: &DBConn) -> Result<Product, Error> {
- todo!()
-}
-
-pub fn delete_product(conn: &DBConn) -> Result<(), Error> {
+pub fn read_products(conn: &DBConn) -> Result<Vec<models::Product>, Error> {
+ use crate::schema::products::dsl::*;
+ let results = products.load::<entities::Product>(conn)?;
todo!()
}
diff --git a/dichroism/src/schema.rs b/dichroism/src/schema.rs
index 5b038e6..9eb92d8 100644
--- a/dichroism/src/schema.rs
+++ b/dichroism/src/schema.rs
@@ -15,7 +15,22 @@ table! {
}
}
+table! {
+ products (id) {
+ id -> Integer,
+ photo_set -> Integer,
+ cents -> Integer,
+ quantity -> Integer,
+ name -> Text,
+ description -> Text,
+ featured -> Integer,
+ }
+}
+
+joinable!(products -> photo_sets (photo_set));
+
allow_tables_to_appear_in_same_query!(
photo_sets,
photos,
+ products,
);