From 13d0c9401e02ad3e40ef73ed5cf587b481b516d2 Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Fri, 2 Feb 2024 20:42:37 -0700 Subject: [PATCH] Added image querying --- Cargo.lock | 22 ++++++++ Cargo.toml | 1 + src/main.rs | 109 ++++++++++++++++++++++++++++++++++++- src/model/image.rs | 3 + src/storage_manager/mod.rs | 5 +- 5 files changed, 137 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53ffedd..8f21323 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1156,6 +1156,7 @@ dependencies = [ "j_db", "log", "multer", + "rand", "serde", "serde_json", "structopt", @@ -1196,6 +1197,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1244,6 +1251,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", "rand_core", ] @@ -1252,6 +1271,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "redox_syscall" diff --git a/Cargo.toml b/Cargo.toml index b402474..889417a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ log = { version = "0.4.20", features = [] } env_logger = "0.11.0" multer = "3.0.0" serde_json = "1.0.111" +rand = "0.8.5" [dependencies.axum] version = "0.7.4" diff --git a/src/main.rs b/src/main.rs index cfd310e..4ea137c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,8 @@ use crate::model::album::Album; use crate::model::image::{Image, ImageData}; use crate::state::Context; use crate::storage_manager::{StorageManager, StoreError}; -use axum::extract::{Multipart, Path, Query, State}; +use axum::body::Bytes; +use axum::extract::{DefaultBodyLimit, Multipart, Path, Query, State}; use axum::http::StatusCode; use axum::response::IntoResponse; use axum::routing::{get, post}; @@ -18,6 +19,8 @@ use axum_macros::FromRequest; use j_db::database::Database; use j_db::model::JdbModel; use log::info; +use rand::seq::SliceRandom; +use rand::thread_rng; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::sync::Arc; @@ -47,6 +50,26 @@ struct AddImage { pub tags: Vec, } +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub enum ImageSort { + Random, + DateAscending, + DateDescending, + #[default] + None, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct ImageQuery { + pub album: Option, + #[serde(default)] + pub tags: Vec, + #[serde(default)] + pub order: ImageSort, + #[serde(default)] + pub limit: usize, +} + #[derive(FromRequest)] #[from_request(via(axum::Json), rejection(PicOxError))] struct Response(T); @@ -60,9 +83,12 @@ where } } +#[allow(dead_code)] enum PicOxError { StoreError(StoreError), DbError(j_db::error::JDbError), + AlbumNotFound, + ImageNotFound, } impl From for PicOxError { @@ -95,6 +121,14 @@ impl IntoResponse for PicOxError { ), }, PicOxError::DbError(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()), + PicOxError::AlbumNotFound => ( + StatusCode::INTERNAL_SERVER_ERROR, + "No album found!".to_string(), + ), + PicOxError::ImageNotFound => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Image not found".to_string(), + ), }; (status, Response(ErrorResponse { message })).into_response() @@ -123,6 +157,73 @@ async fn get_album( Ok(Response(album)) } +async fn query_images( + image_query: Query, + State(context): State, +) -> Result>, PicOxError> { + let album_id = if let Some(album) = &image_query.album { + Some( + Album::find_album_by_query( + &context.db, + AlbumQuery { + album_name: Some(album.to_string()), + }, + ) + .ok_or(PicOxError::AlbumNotFound)? + .id() + .unwrap(), + ) + } else { + None + }; + + let mut images: Vec = context + .db + .filter(|_, img: &Image| { + if let Some(album_id) = album_id { + if img.album != album_id { + return false; + } + } + + if !image_query.tags.is_empty() { + let mut found = false; + for tag in &image_query.tags { + if img.tags.contains(tag) { + found = true; + break; + } + } + + if !found { + return false; + } + } + + true + })? + .collect(); + + match image_query.order { + ImageSort::Random => { + images.shuffle(&mut thread_rng()); + } + ImageSort::DateAscending => { + images.sort_by(|img_a, img_b| img_a.create_date.cmp(&img_b.create_date)) + } + ImageSort::DateDescending => { + images.sort_by(|img_a, img_b| img_b.create_date.cmp(&img_a.create_date)) + } + ImageSort::None => {} + } + + if images.len() > image_query.limit { + images.drain(image_query.limit..); + } + + Ok(Response(images)) +} + async fn add_image( State(context): State, mut img_data: Multipart, @@ -138,7 +239,8 @@ async fn add_image( metadata = Some(serde_json::from_str(&metadata_json).unwrap()) } else if field_name == "img_data" { file_name = Some(field.file_name().unwrap().to_string()); - data.extend_from_slice(field.bytes().await.unwrap().as_ref()); + let file_segment = field.bytes().await.unwrap_or(Bytes::new()); + data.extend_from_slice(file_segment.as_ref()); } } } @@ -154,6 +256,7 @@ async fn add_image( ImageData::Bytes(data), &file_name.unwrap(), 0, + album.id().unwrap(), ) .await?; @@ -199,6 +302,8 @@ async fn main() { .route("/api/album/:id", get(get_album)) .route("/api/album/", get(query_album)) .route("/api/image/", post(add_image)) + .layer(DefaultBodyLimit::max(1024 * 1024 * 1024)) + .route("/api/image/", get(query_images)) .with_state(context); // run our app with hyper, listening globally on port 3000 diff --git a/src/model/image.rs b/src/model/image.rs index 2f38413..bc109e6 100644 --- a/src/model/image.rs +++ b/src/model/image.rs @@ -17,6 +17,7 @@ pub struct Image { pub link: String, pub created_by: u64, pub storage_location: StorageLocation, + pub album: u64, id: Option, } @@ -28,6 +29,7 @@ impl Image { link: Url, created_by: u64, storage_location: StorageLocation, + album: u64, ) -> Self { Self { filename: filename.to_string(), @@ -36,6 +38,7 @@ impl Image { link: link.to_string(), created_by, storage_location, + album, id: None, } } diff --git a/src/storage_manager/mod.rs b/src/storage_manager/mod.rs index 09ba786..d3c8fa4 100644 --- a/src/storage_manager/mod.rs +++ b/src/storage_manager/mod.rs @@ -48,6 +48,7 @@ pub trait Store: Send { &mut self, img_data: ImageData, file_name: &str, + album: u64, created_by: u64, ) -> Result { let (url, storage_location) = self.store_img(img_data, file_name).await?; @@ -58,6 +59,7 @@ pub trait Store: Send { url, created_by, storage_location, + album, )) } @@ -122,6 +124,7 @@ impl StorageManager { img_data: ImageData, file_name: &str, created_by: u64, + album: u64, ) -> Result { let file_name_path = PathBuf::from(file_name); @@ -151,7 +154,7 @@ impl StorageManager { } let img = store_type - .create_img(img_data, file_name, created_by) + .create_img(img_data, file_name, created_by, album) .await?; Ok(db.insert::(img).unwrap())