Added basic image handling
This commit is contained in:
		
							parent
							
								
									be25ac0c5f
								
							
						
					
					
						commit
						e7cdb44714
					
				
							
								
								
									
										36
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										36
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -156,6 +156,7 @@ dependencies = [
 | 
				
			|||||||
 "matchit",
 | 
					 "matchit",
 | 
				
			||||||
 "memchr",
 | 
					 "memchr",
 | 
				
			||||||
 "mime",
 | 
					 "mime",
 | 
				
			||||||
 | 
					 "multer",
 | 
				
			||||||
 "percent-encoding",
 | 
					 "percent-encoding",
 | 
				
			||||||
 "pin-project-lite",
 | 
					 "pin-project-lite",
 | 
				
			||||||
 "rustversion",
 | 
					 "rustversion",
 | 
				
			||||||
@ -415,6 +416,15 @@ version = "0.3.0"
 | 
				
			|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
 | 
					checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "encoding_rs"
 | 
				
			||||||
 | 
					version = "0.8.33"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "cfg-if",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "env_filter"
 | 
					name = "env_filter"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
@ -880,6 +890,24 @@ dependencies = [
 | 
				
			|||||||
 "windows-sys 0.48.0",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "multer"
 | 
				
			||||||
 | 
					version = "3.0.0"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "a15d522be0a9c3e46fd2632e272d178f56387bdb5c9fbb3a36c649062e9b5219"
 | 
				
			||||||
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "bytes",
 | 
				
			||||||
 | 
					 "encoding_rs",
 | 
				
			||||||
 | 
					 "futures-util",
 | 
				
			||||||
 | 
					 "http",
 | 
				
			||||||
 | 
					 "httparse",
 | 
				
			||||||
 | 
					 "log",
 | 
				
			||||||
 | 
					 "memchr",
 | 
				
			||||||
 | 
					 "mime",
 | 
				
			||||||
 | 
					 "spin",
 | 
				
			||||||
 | 
					 "version_check",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "nom"
 | 
					name = "nom"
 | 
				
			||||||
version = "7.1.3"
 | 
					version = "7.1.3"
 | 
				
			||||||
@ -1093,7 +1121,9 @@ dependencies = [
 | 
				
			|||||||
 "hex",
 | 
					 "hex",
 | 
				
			||||||
 "j_db",
 | 
					 "j_db",
 | 
				
			||||||
 "log",
 | 
					 "log",
 | 
				
			||||||
 | 
					 "multer",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
 | 
					 "serde_json",
 | 
				
			||||||
 "sha2",
 | 
					 "sha2",
 | 
				
			||||||
 "structopt",
 | 
					 "structopt",
 | 
				
			||||||
 "tokio",
 | 
					 "tokio",
 | 
				
			||||||
@ -1393,6 +1423,12 @@ dependencies = [
 | 
				
			|||||||
 "windows-sys 0.48.0",
 | 
					 "windows-sys 0.48.0",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[package]]
 | 
				
			||||||
 | 
					name = "spin"
 | 
				
			||||||
 | 
					version = "0.9.8"
 | 
				
			||||||
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
 | 
					checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "strsim"
 | 
					name = "strsim"
 | 
				
			||||||
version = "0.8.0"
 | 
					version = "0.8.0"
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,6 @@ edition = "2021"
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
j_db = { version = "0.1.0", registry = "jojo-dev" }
 | 
					j_db = { version = "0.1.0", registry = "jojo-dev" }
 | 
				
			||||||
axum = "0.7.4"
 | 
					 | 
				
			||||||
axum-macros = "0.4.1"
 | 
					axum-macros = "0.4.1"
 | 
				
			||||||
serde = "1.0.195"
 | 
					serde = "1.0.195"
 | 
				
			||||||
config = "0.13.4"
 | 
					config = "0.13.4"
 | 
				
			||||||
@ -21,6 +20,12 @@ url = "2.5.0"
 | 
				
			|||||||
structopt = "0.3.26"
 | 
					structopt = "0.3.26"
 | 
				
			||||||
log = { version = "0.4.20", features = [] }
 | 
					log = { version = "0.4.20", features = [] }
 | 
				
			||||||
env_logger = "0.11.0"
 | 
					env_logger = "0.11.0"
 | 
				
			||||||
 | 
					multer = "3.0.0"
 | 
				
			||||||
 | 
					serde_json = "1.0.111"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[dependencies.axum]
 | 
				
			||||||
 | 
					version = "0.7.4"
 | 
				
			||||||
 | 
					features = ["multipart"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.tokio]
 | 
					[dependencies.tokio]
 | 
				
			||||||
version = "1.35.1"
 | 
					version = "1.35.1"
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@ -1,22 +1,25 @@
 | 
				
			|||||||
use std::path::PathBuf;
 | 
					use crate::storage_manager::StorageManagerConfig;
 | 
				
			||||||
use config::Config;
 | 
					use config::Config;
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use crate::storage_manager::file_store::FileStoreConfig;
 | 
					use std::path::PathBuf;
 | 
				
			||||||
use crate::storage_manager::StorageTypes;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
pub struct PicOxConfig {
 | 
					pub struct PicOxConfig {
 | 
				
			||||||
    pub host: String,
 | 
					    pub host: String,
 | 
				
			||||||
    pub default_storage_method: StorageTypes,
 | 
					 | 
				
			||||||
    pub db_path: PathBuf,
 | 
					    pub db_path: PathBuf,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub file_store_config: Option<FileStoreConfig>
 | 
					    pub storage_config: StorageManagerConfig,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl PicOxConfig {
 | 
					impl PicOxConfig {
 | 
				
			||||||
    pub fn new(config: PathBuf) -> PicOxConfig {
 | 
					    pub fn new(config: PathBuf) -> PicOxConfig {
 | 
				
			||||||
        let pic_ox_config = Config::builder()
 | 
					        let pic_ox_config = Config::builder()
 | 
				
			||||||
            .add_source(config::File::new(config.to_str().unwrap(), config::FileFormat::Toml)).build().unwrap();
 | 
					            .add_source(config::File::new(
 | 
				
			||||||
 | 
					                config.to_str().unwrap(),
 | 
				
			||||||
 | 
					                config::FileFormat::Toml,
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        pic_ox_config.try_deserialize().unwrap()
 | 
					        pic_ox_config.try_deserialize().unwrap()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										117
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										117
									
								
								src/main.rs
									
									
									
									
									
								
							@ -1,36 +1,55 @@
 | 
				
			|||||||
mod api;
 | 
					mod api;
 | 
				
			||||||
mod model;
 | 
					 | 
				
			||||||
mod storage_manager;
 | 
					 | 
				
			||||||
mod config;
 | 
					mod config;
 | 
				
			||||||
 | 
					mod model;
 | 
				
			||||||
mod state;
 | 
					mod state;
 | 
				
			||||||
 | 
					mod storage_manager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::path::PathBuf;
 | 
					use crate::config::PicOxConfig;
 | 
				
			||||||
use std::sync::Arc;
 | 
					use crate::model::album::Album;
 | 
				
			||||||
use axum::{Json, Router};
 | 
					use crate::model::image::ImageData;
 | 
				
			||||||
use axum::extract::{Path, Query, State};
 | 
					use crate::state::Context;
 | 
				
			||||||
 | 
					use crate::storage_manager::StorageManager;
 | 
				
			||||||
 | 
					use axum::extract::{Multipart, Path, Query, State};
 | 
				
			||||||
use axum::http::StatusCode;
 | 
					use axum::http::StatusCode;
 | 
				
			||||||
use axum::response::IntoResponse;
 | 
					use axum::response::IntoResponse;
 | 
				
			||||||
use axum::routing::{get, post};
 | 
					use axum::routing::{get, post};
 | 
				
			||||||
use axum_macros::debug_handler;
 | 
					use axum::{Json, Router};
 | 
				
			||||||
use j_db::database::Database;
 | 
					use j_db::database::Database;
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use j_db::model::JdbModel;
 | 
				
			||||||
use structopt::StructOpt;
 | 
					 | 
				
			||||||
use log::info;
 | 
					use log::info;
 | 
				
			||||||
use crate::config::PicOxConfig;
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use crate::model::album::Album;
 | 
					use std::path::PathBuf;
 | 
				
			||||||
use crate::state::Context;
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					use structopt::StructOpt;
 | 
				
			||||||
 | 
					use tokio::sync::RwLock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PicContext = Arc<Context>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, StructOpt)]
 | 
					#[derive(Debug, Clone, StructOpt)]
 | 
				
			||||||
struct Args {
 | 
					struct Args {
 | 
				
			||||||
    config: PathBuf
 | 
					    pub config: PathBuf,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
struct CreateAlbum {
 | 
					struct CreateAlbum {
 | 
				
			||||||
    pub album_name: String
 | 
					    pub album_name: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn create_album(State(context): State<Arc<Context>>, Json(album): Json<CreateAlbum>)  -> impl IntoResponse {
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
 | 
					struct AlbumQuery {
 | 
				
			||||||
 | 
					    pub album_name: Option<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
 | 
					struct AddImage {
 | 
				
			||||||
 | 
					    pub album: AlbumQuery,
 | 
				
			||||||
 | 
					    pub image_name: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async fn create_album(
 | 
				
			||||||
 | 
					    State(context): State<PicContext>,
 | 
				
			||||||
 | 
					    Json(album): Json<CreateAlbum>,
 | 
				
			||||||
 | 
					) -> impl IntoResponse {
 | 
				
			||||||
    info!("Creating new album '{}'", album.album_name);
 | 
					    info!("Creating new album '{}'", album.album_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let new_album = Album::new(&album.album_name, Vec::new(), 0);
 | 
					    let new_album = Album::new(&album.album_name, Vec::new(), 0);
 | 
				
			||||||
@ -40,24 +59,57 @@ async fn create_album(State(context): State<Arc<Context>>, Json(album): Json<Cre
 | 
				
			|||||||
    (StatusCode::OK, Json(new_album))
 | 
					    (StatusCode::OK, Json(new_album))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn get_album(album_id: Path<u64>, State(context): State<Arc<Context>>)  -> impl IntoResponse {
 | 
					async fn get_album(album_id: Path<u64>, State(context): State<PicContext>) -> impl IntoResponse {
 | 
				
			||||||
    let album = context.db.get::<Album>(*album_id).unwrap();
 | 
					    let album = context.db.get::<Album>(*album_id).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    (StatusCode::OK, Json(album))
 | 
					    (StatusCode::OK, Json(album))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					async fn add_image(
 | 
				
			||||||
struct AlbumQuery {
 | 
					    State(context): State<PicContext>,
 | 
				
			||||||
    pub album_name: Option<String>
 | 
					    mut img_data: Multipart,
 | 
				
			||||||
 | 
					) -> impl IntoResponse {
 | 
				
			||||||
 | 
					    let mut data: Vec<u8> = Vec::new();
 | 
				
			||||||
 | 
					    let mut metadata: Option<AddImage> = None;
 | 
				
			||||||
 | 
					    while let Some(field) = img_data.next_field().await.unwrap() {
 | 
				
			||||||
 | 
					        let field_name = field.name().clone();
 | 
				
			||||||
 | 
					        if let Some(field_name) = field_name {
 | 
				
			||||||
 | 
					            if field_name == "metadata" {
 | 
				
			||||||
 | 
					                let metadata_json = field.text().await.unwrap();
 | 
				
			||||||
 | 
					                metadata = Some(serde_json::from_str(&metadata_json).unwrap())
 | 
				
			||||||
 | 
					            } else if field_name == "img_data" {
 | 
				
			||||||
 | 
					                data.extend_from_slice(field.bytes().await.unwrap().as_ref());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut album =
 | 
				
			||||||
 | 
					        Album::find_album_by_query(&context.db, metadata.clone().unwrap().album).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut store_manager = context.store_manager.write().await;
 | 
				
			||||||
 | 
					    let img = store_manager
 | 
				
			||||||
 | 
					        .store_img(
 | 
				
			||||||
 | 
					            &context.db,
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            ImageData::Bytes(data),
 | 
				
			||||||
 | 
					            &metadata.unwrap().image_name,
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    album.images.push(img.id().unwrap());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context.db.insert::<Album>(album).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    StatusCode::OK
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn query_album(album_query: Query<AlbumQuery>, State(context): State<Arc<Context>>)  -> impl IntoResponse {
 | 
					async fn query_album(
 | 
				
			||||||
    let resp = if let Some(album_name) = &album_query.album_name {
 | 
					    album_query: Query<AlbumQuery>,
 | 
				
			||||||
        context.db.filter(|_, album: &Album| {album.album_name == *album_name}).unwrap().next()    }
 | 
					    State(context): State<PicContext>,
 | 
				
			||||||
    else {
 | 
					) -> impl IntoResponse {
 | 
				
			||||||
        None
 | 
					    let resp = Album::find_album_by_query(&context.db, album_query.0);
 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    (StatusCode::OK, Json(resp))
 | 
					    (StatusCode::OK, Json(resp))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -69,14 +121,16 @@ async fn main() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let db = Database::new(&config.db_path).unwrap();
 | 
					    let db = Database::new(&config.db_path).unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let store_manager = StorageManager::new(config.storage_config.clone());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let context = Context {
 | 
					    let context = Context {
 | 
				
			||||||
        db,
 | 
					        db,
 | 
				
			||||||
        config,
 | 
					        config: config.clone(),
 | 
				
			||||||
 | 
					        store_manager: RwLock::new(store_manager),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let context = Arc::new(context);
 | 
					    let context = Arc::new(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // initialize tracing
 | 
					    // initialize tracing
 | 
				
			||||||
    tracing_subscriber::fmt::init();
 | 
					    tracing_subscriber::fmt::init();
 | 
				
			||||||
    // build our application with a route
 | 
					    // build our application with a route
 | 
				
			||||||
@ -85,10 +139,11 @@ async fn main() {
 | 
				
			|||||||
        .route("/api/album/create", post(create_album))
 | 
					        .route("/api/album/create", post(create_album))
 | 
				
			||||||
        .route("/api/album/:id", get(get_album))
 | 
					        .route("/api/album/:id", get(get_album))
 | 
				
			||||||
        .route("/api/album/", get(query_album))
 | 
					        .route("/api/album/", get(query_album))
 | 
				
			||||||
        .with_state(context.clone());
 | 
					        .route("/api/image/", post(add_image))
 | 
				
			||||||
 | 
					        .with_state(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // run our app with hyper, listening globally on port 3000
 | 
					    // run our app with hyper, listening globally on port 3000
 | 
				
			||||||
    let listener = tokio::net::TcpListener::bind(&context.config.host).await.unwrap();
 | 
					    let listener = tokio::net::TcpListener::bind(&config.host).await.unwrap();
 | 
				
			||||||
    info!("Serving at {}", context.config.host);
 | 
					    info!("Serving at {}", config.host);
 | 
				
			||||||
    axum::serve(listener, app).await.unwrap();
 | 
					    axum::serve(listener, app).await.unwrap();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
 | 
					use crate::AlbumQuery;
 | 
				
			||||||
use chrono::{DateTime, Utc};
 | 
					use chrono::{DateTime, Utc};
 | 
				
			||||||
 | 
					use j_db::database::Database;
 | 
				
			||||||
use j_db::model::JdbModel;
 | 
					use j_db::model::JdbModel;
 | 
				
			||||||
use j_db::query::QueryBuilder;
 | 
					 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
 | 
					#[derive(Debug, Clone, Deserialize, Serialize)]
 | 
				
			||||||
@ -27,6 +28,16 @@ impl Album {
 | 
				
			|||||||
            id: None,
 | 
					            id: None,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn find_album_by_query(db: &Database, album_query: AlbumQuery) -> Option<Self> {
 | 
				
			||||||
 | 
					        if let Some(album_name) = &album_query.album_name {
 | 
				
			||||||
 | 
					            db.filter(|_, album: &Album| album.album_name == *album_name)
 | 
				
			||||||
 | 
					                .unwrap()
 | 
				
			||||||
 | 
					                .next()
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            None
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl JdbModel for Album {
 | 
					impl JdbModel for Album {
 | 
				
			||||||
@ -46,5 +57,3 @@ impl JdbModel for Album {
 | 
				
			|||||||
        other.album_name != self.album_name
 | 
					        other.album_name != self.album_name
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,12 @@
 | 
				
			|||||||
use std::path::PathBuf;
 | 
					 | 
				
			||||||
use chrono::{DateTime, Utc};
 | 
					use chrono::{DateTime, Utc};
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					use std::path::PathBuf;
 | 
				
			||||||
use url::Url;
 | 
					use url::Url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
pub enum StorageLocation {
 | 
					pub enum StorageLocation {
 | 
				
			||||||
    FileStore {path: PathBuf},
 | 
					    FileStore { path: PathBuf },
 | 
				
			||||||
    Link
 | 
					    Link,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
@ -18,11 +18,17 @@ pub struct Image {
 | 
				
			|||||||
    pub created_by: u64,
 | 
					    pub created_by: u64,
 | 
				
			||||||
    pub storage_location: StorageLocation,
 | 
					    pub storage_location: StorageLocation,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    id: Option<u64>
 | 
					    id: Option<u64>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Image {
 | 
					impl Image {
 | 
				
			||||||
    pub fn new(filename: &str, tags: Vec<String>, link: Url, created_by: u64, storage_location: StorageLocation) -> Self {
 | 
					    pub fn new(
 | 
				
			||||||
 | 
					        filename: &str,
 | 
				
			||||||
 | 
					        tags: Vec<String>,
 | 
				
			||||||
 | 
					        link: Url,
 | 
				
			||||||
 | 
					        created_by: u64,
 | 
				
			||||||
 | 
					        storage_location: StorageLocation,
 | 
				
			||||||
 | 
					    ) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            filename: filename.to_string(),
 | 
					            filename: filename.to_string(),
 | 
				
			||||||
            tags,
 | 
					            tags,
 | 
				
			||||||
@ -52,5 +58,5 @@ impl j_db::model::JdbModel for Image {
 | 
				
			|||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
pub enum ImageData {
 | 
					pub enum ImageData {
 | 
				
			||||||
    Bytes(Vec<u8>),
 | 
					    Bytes(Vec<u8>),
 | 
				
			||||||
    Link(String)
 | 
					    Link(String),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,2 +1,2 @@
 | 
				
			|||||||
pub mod image;
 | 
					 | 
				
			||||||
pub mod album;
 | 
					pub mod album;
 | 
				
			||||||
 | 
					pub mod image;
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,10 @@
 | 
				
			|||||||
use j_db::database::Database;
 | 
					 | 
				
			||||||
use crate::config::PicOxConfig;
 | 
					use crate::config::PicOxConfig;
 | 
				
			||||||
 | 
					use crate::storage_manager::StorageManager;
 | 
				
			||||||
 | 
					use j_db::database::Database;
 | 
				
			||||||
 | 
					use tokio::sync::RwLock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Context {
 | 
					pub struct Context {
 | 
				
			||||||
    pub db: Database,
 | 
					    pub db: Database,
 | 
				
			||||||
    pub config: PicOxConfig
 | 
					    pub config: PicOxConfig,
 | 
				
			||||||
 | 
					    pub store_manager: RwLock<StorageManager>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,11 +1,11 @@
 | 
				
			|||||||
use std::path::PathBuf;
 | 
					use crate::model::image::{Image, ImageData, StorageLocation};
 | 
				
			||||||
 | 
					use crate::storage_manager::{Store, StoreError};
 | 
				
			||||||
use async_trait::async_trait;
 | 
					use async_trait::async_trait;
 | 
				
			||||||
use j_db::database::Database;
 | 
					use j_db::database::Database;
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
use sha2::Digest;
 | 
					use sha2::Digest;
 | 
				
			||||||
 | 
					use std::path::PathBuf;
 | 
				
			||||||
use url::Url;
 | 
					use url::Url;
 | 
				
			||||||
use crate::model::image::{Image, ImageData, StorageLocation};
 | 
					 | 
				
			||||||
use crate::storage_manager::{Store, StoreError};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
pub struct FileStoreConfig {
 | 
					pub struct FileStoreConfig {
 | 
				
			||||||
@ -18,23 +18,25 @@ pub struct FileStoreConfig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
pub struct FileStore {
 | 
					pub struct FileStore {
 | 
				
			||||||
    pub config: FileStoreConfig
 | 
					    pub config: FileStoreConfig,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl FileStore {
 | 
					impl FileStore {
 | 
				
			||||||
    pub fn new(config: FileStoreConfig) -> Self {
 | 
					    pub fn new(config: FileStoreConfig) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self { config }
 | 
				
			||||||
            config
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[async_trait]
 | 
					#[async_trait]
 | 
				
			||||||
impl Store for FileStore {
 | 
					impl Store for FileStore {
 | 
				
			||||||
    async fn store_img(&mut self, img_data: ImageData, file_name: &str) -> Result<(Url, StorageLocation), StoreError> {
 | 
					    async fn store_img(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        img_data: ImageData,
 | 
				
			||||||
 | 
					        file_name: &str,
 | 
				
			||||||
 | 
					    ) -> Result<(Url, StorageLocation), StoreError> {
 | 
				
			||||||
        let img_data = match img_data {
 | 
					        let img_data = match img_data {
 | 
				
			||||||
            ImageData::Bytes(b) => b,
 | 
					            ImageData::Bytes(b) => b,
 | 
				
			||||||
            ImageData::Link(_) => unimplemented!("No link support")
 | 
					            ImageData::Link(_) => unimplemented!("No link support"),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let hash = sha2::Sha256::digest(&img_data);
 | 
					        let hash = sha2::Sha256::digest(&img_data);
 | 
				
			||||||
        let disk_file_name = hex::encode(hash);
 | 
					        let disk_file_name = hex::encode(hash);
 | 
				
			||||||
@ -48,9 +50,12 @@ impl Store for FileStore {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        tokio::fs::write(&path, img_data).await?;
 | 
					        tokio::fs::write(&path, img_data).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let img_link = Url::parse(&self.config.base_url).unwrap().join(&disk_file_name).unwrap();
 | 
					        let img_link = Url::parse(&self.config.base_url)
 | 
				
			||||||
 | 
					            .unwrap()
 | 
				
			||||||
 | 
					            .join(&disk_file_name)
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let storage_location = StorageLocation::FileStore {path};
 | 
					        let storage_location = StorageLocation::FileStore { path };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok((img_link, storage_location))
 | 
					        Ok((img_link, storage_location))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,10 @@
 | 
				
			|||||||
use std::fmt::{Display, Formatter};
 | 
					use crate::model::image::{Image, ImageData, StorageLocation};
 | 
				
			||||||
 | 
					use crate::storage_manager::file_store::{FileStore, FileStoreConfig};
 | 
				
			||||||
use axum::async_trait;
 | 
					use axum::async_trait;
 | 
				
			||||||
use j_db::database::Database;
 | 
					use j_db::database::Database;
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					use std::fmt::{Display, Formatter};
 | 
				
			||||||
use url::Url;
 | 
					use url::Url;
 | 
				
			||||||
use crate::model::image::{Image, ImageData, StorageLocation};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod file_store;
 | 
					pub mod file_store;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -13,7 +14,7 @@ pub enum StoreError {
 | 
				
			|||||||
    ImageNotFound,
 | 
					    ImageNotFound,
 | 
				
			||||||
    OutOfStorage,
 | 
					    OutOfStorage,
 | 
				
			||||||
    ImageTooBig,
 | 
					    ImageTooBig,
 | 
				
			||||||
    IOError(tokio::io::Error)
 | 
					    IOError(tokio::io::Error),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Display for StoreError {
 | 
					impl Display for StoreError {
 | 
				
			||||||
@ -23,7 +24,7 @@ impl Display for StoreError {
 | 
				
			|||||||
            StoreError::ImageNotFound => write!(f, "Image not found"),
 | 
					            StoreError::ImageNotFound => write!(f, "Image not found"),
 | 
				
			||||||
            StoreError::IOError(err) => write!(f, "IO Error: {}", err),
 | 
					            StoreError::IOError(err) => write!(f, "IO Error: {}", err),
 | 
				
			||||||
            StoreError::OutOfStorage => write!(f, "Underlying store full"),
 | 
					            StoreError::OutOfStorage => write!(f, "Underlying store full"),
 | 
				
			||||||
            StoreError::ImageTooBig => write!(f, "Image too big for store")
 | 
					            StoreError::ImageTooBig => write!(f, "Image too big for store"),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -37,14 +38,28 @@ impl From<tokio::io::Error> for StoreError {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[async_trait]
 | 
					#[async_trait]
 | 
				
			||||||
pub trait Store {
 | 
					pub trait Store: Send {
 | 
				
			||||||
    async fn store_img(&mut self, img_data: ImageData, file_name: &str) -> Result<(Url, StorageLocation), StoreError>;
 | 
					    async fn store_img(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
    async fn create_img(&mut self, img_data: ImageData, file_name: &str, created_by: u64) -> Result<Image, StoreError> {
 | 
					        img_data: ImageData,
 | 
				
			||||||
 | 
					        file_name: &str,
 | 
				
			||||||
 | 
					    ) -> Result<(Url, StorageLocation), StoreError>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn create_img(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        img_data: ImageData,
 | 
				
			||||||
 | 
					        file_name: &str,
 | 
				
			||||||
 | 
					        created_by: u64,
 | 
				
			||||||
 | 
					    ) -> Result<Image, StoreError> {
 | 
				
			||||||
        let (url, storage_location) = self.store_img(img_data, file_name).await?;
 | 
					        let (url, storage_location) = self.store_img(img_data, file_name).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(Image::new(file_name, Vec::new(), url, created_by, storage_location))
 | 
					        Ok(Image::new(
 | 
				
			||||||
 | 
					            file_name,
 | 
				
			||||||
 | 
					            Vec::new(),
 | 
				
			||||||
 | 
					            url,
 | 
				
			||||||
 | 
					            created_by,
 | 
				
			||||||
 | 
					            storage_location,
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn delete_img(&mut self, img: Image) -> StoreError;
 | 
					    async fn delete_img(&mut self, img: Image) -> StoreError;
 | 
				
			||||||
@ -56,18 +71,71 @@ pub trait Store {
 | 
				
			|||||||
    fn current_store_size(&self, db: &Database) -> usize;
 | 
					    fn current_store_size(&self, db: &Database) -> usize;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
 | 
				
			||||||
pub enum StorageTypes {
 | 
					pub enum StorageTypes {
 | 
				
			||||||
    FileStore,
 | 
					    FileStore,
 | 
				
			||||||
    LinkStore
 | 
					    LinkStore,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
					#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
				
			||||||
pub struct StorageManagerConfig {
 | 
					pub struct StorageManagerConfig {
 | 
				
			||||||
 | 
					    pub default_storage_method: StorageTypes,
 | 
				
			||||||
 | 
					    pub file_store_config: Option<FileStoreConfig>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					#[derive(Debug, Clone)]
 | 
				
			||||||
pub struct StorageManager {
 | 
					pub struct StorageManager {
 | 
				
			||||||
 | 
					    config: StorageManagerConfig,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    file_store: Option<FileStore>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl StorageManager {
 | 
				
			||||||
 | 
					    pub fn new(storage_manager_config: StorageManagerConfig) -> Self {
 | 
				
			||||||
 | 
					        let file_store =
 | 
				
			||||||
 | 
					            if let Some(file_store_config) = storage_manager_config.file_store_config.clone() {
 | 
				
			||||||
 | 
					                Some(FileStore::new(file_store_config))
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                None
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            config: storage_manager_config,
 | 
				
			||||||
 | 
					            file_store,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_default_storage_manager(&mut self) -> Box<&mut dyn Store> {
 | 
				
			||||||
 | 
					        self.get_storage_manager(self.config.default_storage_method)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn get_storage_manager(&mut self, storage_type: StorageTypes) -> Box<&mut dyn Store> {
 | 
				
			||||||
 | 
					        match storage_type {
 | 
				
			||||||
 | 
					            StorageTypes::FileStore => Box::new(self.file_store.as_mut().unwrap()),
 | 
				
			||||||
 | 
					            StorageTypes::LinkStore => {
 | 
				
			||||||
 | 
					                unimplemented!()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn store_img(
 | 
				
			||||||
 | 
					        &mut self,
 | 
				
			||||||
 | 
					        db: &Database,
 | 
				
			||||||
 | 
					        store: Option<StorageTypes>,
 | 
				
			||||||
 | 
					        img_data: ImageData,
 | 
				
			||||||
 | 
					        file_name: &str,
 | 
				
			||||||
 | 
					        created_by: u64,
 | 
				
			||||||
 | 
					    ) -> Result<Image, StoreError> {
 | 
				
			||||||
 | 
					        let store_type = if let Some(store_type) = store {
 | 
				
			||||||
 | 
					            self.get_storage_manager(store_type)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            self.get_default_storage_manager()
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let img = store_type
 | 
				
			||||||
 | 
					            .create_img(img_data, file_name, created_by)
 | 
				
			||||||
 | 
					            .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Ok(db.insert::<Image>(img).unwrap())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user