mirror of
https://github.com/perstarkse/minne.git
synced 2026-01-18 07:56:44 +01:00
refactor: upload files with ingress call
This commit is contained in:
@@ -20,7 +20,7 @@ use zettle_db::{
|
||||
middleware_api_auth::api_auth,
|
||||
routes::{
|
||||
api::{
|
||||
file::upload_handler, ingress::ingress_handler, query::query_handler,
|
||||
file::upload_handler, ingress::ingress_data, query::query_handler,
|
||||
queue_length::queue_length_handler,
|
||||
},
|
||||
html::{
|
||||
@@ -132,7 +132,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
fn api_routes_v1(app_state: &AppState) -> Router<AppState> {
|
||||
Router::new()
|
||||
// Ingress routes
|
||||
.route("/ingress", post(ingress_handler))
|
||||
.route("/ingress", post(ingress_data))
|
||||
.route("/message_count", get(queue_length_handler))
|
||||
// File routes
|
||||
.route("/file", post(upload_handler))
|
||||
|
||||
@@ -16,20 +16,19 @@ pub struct IngressInput {
|
||||
pub content: Option<String>,
|
||||
pub instructions: String,
|
||||
pub category: String,
|
||||
pub files: Option<Vec<String>>,
|
||||
pub files: Vec<FileInfo>,
|
||||
}
|
||||
|
||||
/// Function to create ingress objects from input.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `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
|
||||
/// * `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,
|
||||
db_client: &SurrealDbClient,
|
||||
user_id: &str,
|
||||
) -> Result<Vec<IngressObject>, AppError> {
|
||||
// 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
|
||||
if let Some(file_ids) = input.files {
|
||||
for id in file_ids {
|
||||
if let Some(file_info) = get_item::<FileInfo>(db_client, &id).await? {
|
||||
object_list.push(IngressObject::File {
|
||||
file_info,
|
||||
instructions: input.instructions.clone(),
|
||||
category: input.category.clone(),
|
||||
user_id: user_id.into(),
|
||||
});
|
||||
} else {
|
||||
info!("No file with id: {}", id);
|
||||
}
|
||||
}
|
||||
for file in input.files {
|
||||
object_list.push(IngressObject::File {
|
||||
file_info: file,
|
||||
instructions: input.instructions.clone(),
|
||||
category: input.category.clone(),
|
||||
user_id: user_id.into(),
|
||||
})
|
||||
}
|
||||
|
||||
// If no objects are constructed, we return Err
|
||||
|
||||
@@ -1,21 +1,49 @@
|
||||
use crate::{
|
||||
error::{ApiError, AppError},
|
||||
ingress::types::ingress_input::{create_ingress_objects, IngressInput},
|
||||
ingress::types::{
|
||||
ingress_input::{create_ingress_objects, IngressInput},
|
||||
ingress_object,
|
||||
},
|
||||
server::AppState,
|
||||
storage::types::user::User,
|
||||
storage::types::{file_info::FileInfo, user::User},
|
||||
};
|
||||
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;
|
||||
|
||||
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>,
|
||||
Extension(user): Extension<User>,
|
||||
Json(input): Json<IngressInput>,
|
||||
TypedMultipart(input): TypedMultipart<IngressParams>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
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
|
||||
.into_iter()
|
||||
|
||||
@@ -43,6 +43,7 @@ pub struct IngressParams {
|
||||
pub instructions: String,
|
||||
pub category: String,
|
||||
#[form_data(limit = "10000000")] // Adjust limit as needed
|
||||
#[form_data(default)]
|
||||
pub files: Vec<FieldData<NamedTempFile>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ impl FileInfo {
|
||||
pub async fn new(
|
||||
field_data: FieldData<NamedTempFile>,
|
||||
db_client: &SurrealDbClient,
|
||||
user_id: &str,
|
||||
) -> Result<Self, FileError> {
|
||||
let file = field_data.contents;
|
||||
let file_name = field_data
|
||||
@@ -74,7 +75,7 @@ impl FileInfo {
|
||||
let file_info = Self {
|
||||
id: uuid.to_string(),
|
||||
sha256,
|
||||
path: Self::persist_file(&uuid, file, &sanitized_file_name)
|
||||
path: Self::persist_file(&uuid, file, &sanitized_file_name, user_id)
|
||||
.await?
|
||||
.to_string_lossy()
|
||||
.into(),
|
||||
@@ -139,12 +140,13 @@ impl FileInfo {
|
||||
.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
|
||||
/// * `uuid` - The UUID of the file.
|
||||
/// * `file` - The temporary file to persist.
|
||||
/// * `file_name` - The sanitized file name.
|
||||
/// * `user-id` - User id
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Result<PathBuf, FileError>` - The persisted file path or an error.
|
||||
@@ -152,11 +154,13 @@ impl FileInfo {
|
||||
uuid: &Uuid,
|
||||
file: NamedTempFile,
|
||||
file_name: &str,
|
||||
user_id: &str,
|
||||
) -> Result<PathBuf, FileError> {
|
||||
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)
|
||||
.await
|
||||
.map_err(FileError::Io)?;
|
||||
@@ -167,7 +171,6 @@ impl FileInfo {
|
||||
|
||||
// Persist the temporary file to the final path
|
||||
file.persist(&final_path)?;
|
||||
|
||||
info!("Persisted file to {:?}", final_path);
|
||||
|
||||
Ok(final_path)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar bg-base-200">
|
||||
<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>
|
||||
<ul class="menu menu-horizontal px-1">
|
||||
|
||||
Reference in New Issue
Block a user