mirror of
https://github.com/perstarkse/minne.git
synced 2026-04-17 22:49:43 +02:00
refactor: upload files with ingress call
This commit is contained in:
@@ -20,7 +20,7 @@ use zettle_db::{
|
|||||||
middleware_api_auth::api_auth,
|
middleware_api_auth::api_auth,
|
||||||
routes::{
|
routes::{
|
||||||
api::{
|
api::{
|
||||||
file::upload_handler, ingress::ingress_handler, query::query_handler,
|
file::upload_handler, ingress::ingress_data, query::query_handler,
|
||||||
queue_length::queue_length_handler,
|
queue_length::queue_length_handler,
|
||||||
},
|
},
|
||||||
html::{
|
html::{
|
||||||
@@ -132,7 +132,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
fn api_routes_v1(app_state: &AppState) -> Router<AppState> {
|
fn api_routes_v1(app_state: &AppState) -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
// Ingress routes
|
// Ingress routes
|
||||||
.route("/ingress", post(ingress_handler))
|
.route("/ingress", post(ingress_data))
|
||||||
.route("/message_count", get(queue_length_handler))
|
.route("/message_count", get(queue_length_handler))
|
||||||
// File routes
|
// File routes
|
||||||
.route("/file", post(upload_handler))
|
.route("/file", post(upload_handler))
|
||||||
|
|||||||
@@ -16,20 +16,19 @@ pub struct IngressInput {
|
|||||||
pub content: Option<String>,
|
pub content: Option<String>,
|
||||||
pub instructions: String,
|
pub instructions: String,
|
||||||
pub category: String,
|
pub category: String,
|
||||||
pub files: Option<Vec<String>>,
|
pub files: Vec<FileInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function to create ingress objects from input.
|
/// Function to create ingress objects from input.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `input` - IngressInput containing information needed to ingress content.
|
/// * `input` - IngressInput containing information needed to ingress content.
|
||||||
/// * `redis_client` - Initialized redis client needed to retrieve file information
|
/// * `user_id` - User id of the ingressing user
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `Vec<IngressObject>` - An array containing the ingressed objects, one file/contenttype per object.
|
/// * `Vec<IngressObject>` - An array containing the ingressed objects, one file/contenttype per object.
|
||||||
pub async fn create_ingress_objects(
|
pub fn create_ingress_objects(
|
||||||
input: IngressInput,
|
input: IngressInput,
|
||||||
db_client: &SurrealDbClient,
|
|
||||||
user_id: &str,
|
user_id: &str,
|
||||||
) -> Result<Vec<IngressObject>, AppError> {
|
) -> Result<Vec<IngressObject>, AppError> {
|
||||||
// Initialize list
|
// Initialize list
|
||||||
@@ -59,20 +58,13 @@ pub async fn create_ingress_objects(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up FileInfo objects using the db and the submitted uuids in input.files
|
for file in input.files {
|
||||||
if let Some(file_ids) = input.files {
|
object_list.push(IngressObject::File {
|
||||||
for id in file_ids {
|
file_info: file,
|
||||||
if let Some(file_info) = get_item::<FileInfo>(db_client, &id).await? {
|
instructions: input.instructions.clone(),
|
||||||
object_list.push(IngressObject::File {
|
category: input.category.clone(),
|
||||||
file_info,
|
user_id: user_id.into(),
|
||||||
instructions: input.instructions.clone(),
|
})
|
||||||
category: input.category.clone(),
|
|
||||||
user_id: user_id.into(),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
info!("No file with id: {}", id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no objects are constructed, we return Err
|
// If no objects are constructed, we return Err
|
||||||
|
|||||||
@@ -1,21 +1,49 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
error::{ApiError, AppError},
|
error::{ApiError, AppError},
|
||||||
ingress::types::ingress_input::{create_ingress_objects, IngressInput},
|
ingress::types::{
|
||||||
|
ingress_input::{create_ingress_objects, IngressInput},
|
||||||
|
ingress_object,
|
||||||
|
},
|
||||||
server::AppState,
|
server::AppState,
|
||||||
storage::types::user::User,
|
storage::types::{file_info::FileInfo, user::User},
|
||||||
};
|
};
|
||||||
use axum::{extract::State, http::StatusCode, response::IntoResponse, Extension, Json};
|
use axum::{extract::State, http::StatusCode, response::IntoResponse, Extension, Json};
|
||||||
use futures::future::try_join_all;
|
use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart};
|
||||||
|
use futures::{future::try_join_all, TryFutureExt};
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
pub async fn ingress_handler(
|
#[derive(Debug, TryFromMultipart)]
|
||||||
|
pub struct IngressParams {
|
||||||
|
pub content: Option<String>,
|
||||||
|
pub instructions: String,
|
||||||
|
pub category: String,
|
||||||
|
#[form_data(limit = "10000000")] // Adjust limit as needed
|
||||||
|
#[form_data(default)]
|
||||||
|
pub files: Vec<FieldData<NamedTempFile>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn ingress_data(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Extension(user): Extension<User>,
|
Extension(user): Extension<User>,
|
||||||
Json(input): Json<IngressInput>,
|
TypedMultipart(input): TypedMultipart<IngressParams>,
|
||||||
) -> Result<impl IntoResponse, ApiError> {
|
) -> Result<impl IntoResponse, ApiError> {
|
||||||
info!("Received input: {:?}", input);
|
info!("Received input: {:?}", input);
|
||||||
|
|
||||||
let ingress_objects = create_ingress_objects(input, &state.surreal_db_client, &user.id).await?;
|
let file_infos = try_join_all(input.files.into_iter().map(|file| {
|
||||||
|
FileInfo::new(file, &state.surreal_db_client, &user.id).map_err(|e| AppError::from(e))
|
||||||
|
}))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let ingress_objects = create_ingress_objects(
|
||||||
|
IngressInput {
|
||||||
|
content: input.content,
|
||||||
|
instructions: input.instructions,
|
||||||
|
category: input.category,
|
||||||
|
files: file_infos,
|
||||||
|
},
|
||||||
|
user.id.as_str(),
|
||||||
|
)?;
|
||||||
|
|
||||||
let futures: Vec<_> = ingress_objects
|
let futures: Vec<_> = ingress_objects
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ pub struct IngressParams {
|
|||||||
pub instructions: String,
|
pub instructions: String,
|
||||||
pub category: String,
|
pub category: String,
|
||||||
#[form_data(limit = "10000000")] // Adjust limit as needed
|
#[form_data(limit = "10000000")] // Adjust limit as needed
|
||||||
|
#[form_data(default)]
|
||||||
pub files: Vec<FieldData<NamedTempFile>>,
|
pub files: Vec<FieldData<NamedTempFile>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ impl FileInfo {
|
|||||||
pub async fn new(
|
pub async fn new(
|
||||||
field_data: FieldData<NamedTempFile>,
|
field_data: FieldData<NamedTempFile>,
|
||||||
db_client: &SurrealDbClient,
|
db_client: &SurrealDbClient,
|
||||||
|
user_id: &str,
|
||||||
) -> Result<Self, FileError> {
|
) -> Result<Self, FileError> {
|
||||||
let file = field_data.contents;
|
let file = field_data.contents;
|
||||||
let file_name = field_data
|
let file_name = field_data
|
||||||
@@ -74,7 +75,7 @@ impl FileInfo {
|
|||||||
let file_info = Self {
|
let file_info = Self {
|
||||||
id: uuid.to_string(),
|
id: uuid.to_string(),
|
||||||
sha256,
|
sha256,
|
||||||
path: Self::persist_file(&uuid, file, &sanitized_file_name)
|
path: Self::persist_file(&uuid, file, &sanitized_file_name, user_id)
|
||||||
.await?
|
.await?
|
||||||
.to_string_lossy()
|
.to_string_lossy()
|
||||||
.into(),
|
.into(),
|
||||||
@@ -139,12 +140,13 @@ impl FileInfo {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Persists the file to the filesystem under `./data/{uuid}/{file_name}`.
|
/// Persists the file to the filesystem under `./data/{user_id}/{uuid}/{file_name}`.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `uuid` - The UUID of the file.
|
/// * `uuid` - The UUID of the file.
|
||||||
/// * `file` - The temporary file to persist.
|
/// * `file` - The temporary file to persist.
|
||||||
/// * `file_name` - The sanitized file name.
|
/// * `file_name` - The sanitized file name.
|
||||||
|
/// * `user-id` - User id
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `Result<PathBuf, FileError>` - The persisted file path or an error.
|
/// * `Result<PathBuf, FileError>` - The persisted file path or an error.
|
||||||
@@ -152,11 +154,13 @@ impl FileInfo {
|
|||||||
uuid: &Uuid,
|
uuid: &Uuid,
|
||||||
file: NamedTempFile,
|
file: NamedTempFile,
|
||||||
file_name: &str,
|
file_name: &str,
|
||||||
|
user_id: &str,
|
||||||
) -> Result<PathBuf, FileError> {
|
) -> Result<PathBuf, FileError> {
|
||||||
let base_dir = Path::new("./data");
|
let base_dir = Path::new("./data");
|
||||||
let uuid_dir = base_dir.join(uuid.to_string());
|
let user_dir = base_dir.join(user_id); // Create the user directory
|
||||||
|
let uuid_dir = user_dir.join(uuid.to_string()); // Create the UUID directory under the user directory
|
||||||
|
|
||||||
// Create the UUID directory if it doesn't exist
|
// Create the user and UUID directories if they don't exist
|
||||||
tokio::fs::create_dir_all(&uuid_dir)
|
tokio::fs::create_dir_all(&uuid_dir)
|
||||||
.await
|
.await
|
||||||
.map_err(FileError::Io)?;
|
.map_err(FileError::Io)?;
|
||||||
@@ -167,7 +171,6 @@ impl FileInfo {
|
|||||||
|
|
||||||
// Persist the temporary file to the final path
|
// Persist the temporary file to the final path
|
||||||
file.persist(&final_path)?;
|
file.persist(&final_path)?;
|
||||||
|
|
||||||
info!("Persisted file to {:?}", final_path);
|
info!("Persisted file to {:?}", final_path);
|
||||||
|
|
||||||
Ok(final_path)
|
Ok(final_path)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<!-- Navbar -->
|
<!-- Navbar -->
|
||||||
<nav class="navbar bg-base-200">
|
<nav class="navbar bg-base-200">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<a class="btn text-2xl border-transparent btn-outline btn-primary" href="/">Minne</a>
|
<a class="btn text-2xl border-transparent btn-outline btn-primary" href="/" hx-boost="true">Minne</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ul class="menu menu-horizontal px-1">
|
<ul class="menu menu-horizontal px-1">
|
||||||
|
|||||||
Reference in New Issue
Block a user