refactor: upload files with ingress call

This commit is contained in:
Per Stark
2025-01-03 15:27:14 +01:00
parent 69a0064250
commit 29dc0db7bc
7 changed files with 57 additions and 32 deletions

View File

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

View File

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

View File

@@ -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()

View File

@@ -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>>,
}

View File

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

View File

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

View File

@@ -7,3 +7,4 @@
\[\] add more config structs for clarity
\[\] user id to fileinfo and data path?
\[x\] gdpr
\[\] ios shortcut generation