Added basic image handling
parent
be25ac0c5f
commit
e7cdb44714
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
119
src/main.rs
119
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,13 +121,15 @@ 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();
|
||||||
|
@ -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…
Reference in New Issue