Added image querying

main
Joey Hines 2024-02-02 20:42:37 -07:00
parent a6fe143075
commit 13d0c9401e
No known key found for this signature in database
GPG Key ID: 995E531F7A569DDB
5 changed files with 137 additions and 3 deletions

22
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub enum ImageSort {
Random,
DateAscending,
DateDescending,
#[default]
None,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ImageQuery {
pub album: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub order: ImageSort,
#[serde(default)]
pub limit: usize,
}
#[derive(FromRequest)]
#[from_request(via(axum::Json), rejection(PicOxError))]
struct Response<T>(T);
@ -60,9 +83,12 @@ where
}
}
#[allow(dead_code)]
enum PicOxError {
StoreError(StoreError),
DbError(j_db::error::JDbError),
AlbumNotFound,
ImageNotFound,
}
impl From<StoreError> 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<ImageQuery>,
State(context): State<PicContext>,
) -> Result<Response<Vec<Image>>, 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<Image> = 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<PicContext>,
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

View File

@ -17,6 +17,7 @@ pub struct Image {
pub link: String,
pub created_by: u64,
pub storage_location: StorageLocation,
pub album: u64,
id: Option<u64>,
}
@ -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,
}
}

View File

@ -48,6 +48,7 @@ pub trait Store: Send {
&mut self,
img_data: ImageData,
file_name: &str,
album: u64,
created_by: u64,
) -> Result<Image, StoreError> {
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<Image, StoreError> {
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::<Image>(img).unwrap())