diff --git a/data/55340b32-e38e-48ed-b472-902c3144fe70/changes.md b/data/55340b32-e38e-48ed-b472-902c3144fe70/changes.md new file mode 100644 index 0000000..5a76017 --- /dev/null +++ b/data/55340b32-e38e-48ed-b472-902c3144fe70/changes.md @@ -0,0 +1,33 @@ +Your proposed structure for the API sounds solid and modular, making it easier to manage files and their relationships with other data. Here’s a breakdown of how this can work and how it could be used with an iOS shortcut or a custom application: + +API Structure +File Management Endpoints: + +POST /file: Accepts a file upload and returns a unique identifier (ID) for that file. +PUT /file/{id}: Updates the metadata of the file identified by {id}. +DELETE /file/{id}: Deletes the file and its associated metadata from the database. +Data Ingress Endpoint: + +POST /ingress: Accepts a JSON body containing references (IDs) to files and other necessary data, linking them in the database as needed. +Using with iOS Shortcuts +You can create shortcuts to interact with your API endpoints without the need for a full-fledged application. Here's how: + +File Upload Shortcut: + +Use the "Get File" action to select a file from the device. +Use the "Post" action to send a multipart/form-data request to the /file endpoint. +Parse the response to get the returned file ID. +Data Ingress Shortcut: + +Use the "Ask for Input" action to gather the necessary fields for the ingress (like instructions, category, etc.) and the file ID(s). +Use another "Post" action to send this data to the /ingress endpoint as JSON. +Developing a CLI Tool +A CLI tool could also be developed for easier interaction with your API. This tool could: + +Upload Files: Handle file uploads and return file IDs. +Link Data: Accept user input for instructions, category, and linked file IDs, then submit this data to the /ingress endpoint. +Additional Considerations +Error Handling: Ensure that both the upload and ingress endpoints handle errors gracefully and provide meaningful messages. +Documentation: Create clear API documentation to guide users in constructing requests correctly, whether they are using a shortcut, CLI, or custom application. +Authentication: Consider adding authentication to your API endpoints for security, especially if sensitive data is being handled. +This approach gives you the flexibility to support various clients, streamline interactions, and keep the server-side implementation clean and manageable. Would you like more specifics on any part of this setup? diff --git a/src/models/ingress.rs b/src/models/ingress.rs index b7442d8..d0c125c 100644 --- a/src/models/ingress.rs +++ b/src/models/ingress.rs @@ -1,7 +1,12 @@ +use std::hash::Hash; + use serde::{Deserialize, Serialize}; use thiserror::Error; use tracing::info; use url::Url; +use uuid::Uuid; +use crate::redis::client::RedisClient; + use super::file_info::FileInfo; #[derive(Debug, Deserialize, Serialize)] @@ -48,7 +53,8 @@ pub enum IngressContentError { impl IngressContent { /// Create a new `IngressContent` from `IngressInput`. pub async fn new( - input: IngressInput + input: IngressInput, + redis_client: &RedisClient, // Add RedisClient as a parameter ) -> Result { let content = if let Some(input_content) = input.content { // Check if the content is a URL @@ -63,15 +69,26 @@ impl IngressContent { None }; - // Check if there is files - // Use FileInfo.get(id) with all the ids in files vec - // Return a vec as file_info_list + // Fetch file information if file UUIDs are provided + let files = if let Some(file_uuids) = input.files { + let mut file_info_list = Vec::new(); + for uuid_str in file_uuids { + let uuid = Uuid::parse_str(&uuid_str).map_err(|_| IngressContentError::UnsupportedMime("Invalid UUID".into()))?; + match FileInfo::get(uuid, redis_client).await { + Ok(file_info) => file_info_list.push(file_info), + Err(_) => info!("No file with that uuid"), + } + } + Some(file_info_list) + } else { + None + }; Ok(IngressContent { content, instructions: input.instructions, category: input.category, - files: None, + files, }) } } diff --git a/src/rabbitmq/consumer.rs b/src/rabbitmq/consumer.rs index 4c062f1..2a7bd60 100644 --- a/src/rabbitmq/consumer.rs +++ b/src/rabbitmq/consumer.rs @@ -6,7 +6,6 @@ use crate::models::ingress::IngressContent; use super::{RabbitMQCommon, RabbitMQConfig, RabbitMQError}; use tracing::{info, error}; -use tokio::fs; /// Struct to consume messages from RabbitMQ. pub struct RabbitMQConsumer { @@ -16,6 +15,16 @@ pub struct RabbitMQConsumer { } impl RabbitMQConsumer { + //// Creates a new 'RabbitMQConsumer' instance which sets up a rabbitmq client, + //// declares a exchange if needed, declares and binds a queue and initializes the consumer + //// + //// # Arguments + //// + //// * 'config' - RabbitMQConfig + //// + //// # Returns + //// + //// * 'Result' - The created client or an error. pub async fn new(config: &RabbitMQConfig) -> Result { let common = RabbitMQCommon::new(config).await?; @@ -99,7 +108,7 @@ impl RabbitMQConsumer { loop { match self.consume().await { Ok((ingress, delivery)) => { - info!("Received ingress object: {:?}", ingress); + // info!("Received ingress object: {:?}", ingress); // Process the ingress object self.handle_ingress_content(&ingress).await; diff --git a/src/routes/file.rs b/src/routes/file.rs index ef764b1..55eb5a2 100644 --- a/src/routes/file.rs +++ b/src/routes/file.rs @@ -1,5 +1,3 @@ -// === Contents of ./routes/file.rs === - use axum::{ extract::Path, response::IntoResponse, @@ -9,13 +7,12 @@ use axum::{ use axum_typed_multipart::{TypedMultipart, FieldData, TryFromMultipart}; use serde_json::json; use tempfile::NamedTempFile; -use tracing::{error, info}; +use tracing::info; use uuid::Uuid; use crate::{ models::file_info::{FileError, FileInfo}, redis::client::RedisClient, - rabbitmq::publisher::RabbitMQProducer, }; #[derive(Debug, TryFromMultipart)] diff --git a/src/routes/ingress.rs b/src/routes/ingress.rs index 18ef395..d0fcd74 100644 --- a/src/routes/ingress.rs +++ b/src/routes/ingress.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use axum::{http::StatusCode, response::IntoResponse, Extension, Json}; use tracing::{error, info}; -use crate::{models::ingress::{IngressContent, IngressInput}, rabbitmq::publisher::RabbitMQProducer}; +use crate::{models::ingress::{IngressContent, IngressInput}, rabbitmq::publisher::RabbitMQProducer, redis::client::RedisClient}; pub async fn ingress_handler( Extension(producer): Extension>, @@ -11,7 +11,8 @@ pub async fn ingress_handler( ) -> impl IntoResponse { info!("Recieved input: {:?}", input); - if let Ok(content) = IngressContent::new(input).await { + let redis_client = RedisClient::new("redis://127.0.0.1/"); + if let Ok(content) = IngressContent::new(input, &redis_client).await { // Publish content to RabbitMQ (or other system) match producer.publish(&content).await {