feat: implement dynamo insert with file upload
This commit is contained in:
1 parent
5cafc7e757
commit
1bccecdc72
3 files changed
+104
-50
No files matched your search
+79
-20
@@ -1,13 +1,13 @@
|
||||
use leptos::prelude::*;
|
||||
use server_fn::codec::{MultipartFormData, MultipartData};
|
||||
use leptos::logging::debug_log;
|
||||
use leptos::logging::debug_log;
|
||||
use leptos::prelude::*;
|
||||
use server_fn::codec::{MultipartData, MultipartFormData};
|
||||
|
||||
#[server(input = MultipartFormData)]
|
||||
pub async fn upload_file(
|
||||
data: MultipartData
|
||||
) -> Result<(), ServerFnError> {
|
||||
pub async fn upload_file(data: MultipartData) -> Result<(), ServerFnError> {
|
||||
use super::{MAIN_TABLE_NAME, S3_BUCKET_NAME};
|
||||
use crate::pages::api::tokens::validate_and_get_token_info;
|
||||
use crate::utils::server::create_aws_config;
|
||||
use aws_sdk_dynamodb::types::AttributeValue;
|
||||
|
||||
let mut multipart = data.into_inner().unwrap();
|
||||
|
||||
@@ -15,6 +15,7 @@ pub async fn upload_file(
|
||||
let mut file_bytes = Vec::<u8>::new();
|
||||
let mut form_id = String::new();
|
||||
let mut access_token = String::new();
|
||||
let mut input_name = String::new();
|
||||
|
||||
while let Ok(Some(field)) = multipart.next_field().await {
|
||||
let field_name = field.name().unwrap_or_default();
|
||||
@@ -26,46 +27,104 @@ pub async fn upload_file(
|
||||
// NOTE: If there are multiple files put here the function will only upload the last
|
||||
// one in the data.
|
||||
// We should likely use chunk streaming in the future, for safety.
|
||||
file_name = field.file_name()
|
||||
input_name = field
|
||||
.name()
|
||||
.ok_or(ServerFnError::new("Couldn't find input name"))?
|
||||
.to_owned();
|
||||
file_name = field
|
||||
.file_name()
|
||||
.ok_or(ServerFnError::new("Couldn't find file name"))?
|
||||
.to_owned();
|
||||
file_bytes = field.bytes().await?.to_vec();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if access_token.is_empty() { return Err(ServerFnError::new("Missing access token")); }
|
||||
if form_id.is_empty() { return Err(ServerFnError::new("Missing form ID")); }
|
||||
if file_name.is_empty() { return Err(ServerFnError::new("Missing file")); }
|
||||
if access_token.is_empty() {
|
||||
return Err(ServerFnError::new("Missing access token"));
|
||||
}
|
||||
if form_id.is_empty() {
|
||||
return Err(ServerFnError::new("Missing form ID"));
|
||||
}
|
||||
if file_name.is_empty() {
|
||||
return Err(ServerFnError::new("Missing file"));
|
||||
}
|
||||
if input_name.is_empty() {
|
||||
return Err(ServerFnError::new("Missing input file"));
|
||||
}
|
||||
|
||||
let user_claims = validate_and_get_token_info(access_token).await?;
|
||||
let subject = user_claims.subject;
|
||||
|
||||
let key = format!("{form_id}/{subject}/{file_name}");
|
||||
let key = format!("{form_id}/{subject}/{input_name}/{file_name}");
|
||||
|
||||
debug_log!("Adding file to S3: {:?}", key);
|
||||
|
||||
let client = aws_sdk_s3::Client::new(&create_aws_config().await);
|
||||
let s3_client = aws_sdk_s3::Client::new(&create_aws_config().await);
|
||||
|
||||
client
|
||||
s3_client
|
||||
.put_object()
|
||||
.bucket("leptos-scholarships")
|
||||
.key(key)
|
||||
.bucket(S3_BUCKET_NAME)
|
||||
.key(&key)
|
||||
.body(file_bytes.into())
|
||||
.send()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(ServerFnError::from)
|
||||
.map_err(|e| ServerFnError::new(format!("Couldn't put file to S3: {}", e.to_string())))?;
|
||||
|
||||
// Besides uploading the file to S3, we also want to store a FILE entry in Dynamo.
|
||||
// This will allow the server to quickly return a list of all files that a specific user has
|
||||
// uploaded, or a list of all files submitted for a specific question with their keys for
|
||||
// additional access.
|
||||
let dynamo_client = aws_sdk_dynamodb::Client::new(&create_aws_config().await);
|
||||
|
||||
let dynamo_result = dynamo_client
|
||||
.put_item()
|
||||
.table_name(MAIN_TABLE_NAME)
|
||||
.item(
|
||||
"HK".to_string(),
|
||||
AttributeValue::S(format!("STUDENT#{subject}")),
|
||||
)
|
||||
.item(
|
||||
"SK".to_string(),
|
||||
AttributeValue::S(format!("FILE#{form_id}#{input_name}")),
|
||||
)
|
||||
.item("file_name".to_string(), AttributeValue::S(file_name))
|
||||
.item("file_key".to_string(), AttributeValue::S(key.clone()))
|
||||
.send()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| ServerFnError::new(format!("Couldn't put file to Dynamo: {}", e.to_string())));
|
||||
|
||||
if let Err(_) = dynamo_result {
|
||||
// Roll back file insertion to S3. Only attempted on Dynamo insertion failure.
|
||||
s3_client
|
||||
.delete_object()
|
||||
.bucket("leptos-scholarships")
|
||||
.key(&key)
|
||||
.send()
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
ServerFnError::new(format!(
|
||||
"Couldn't roll back S3 operation: {}",
|
||||
e.to_string()
|
||||
))
|
||||
})
|
||||
} else {
|
||||
dynamo_result
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn delete_file(
|
||||
access_token: String,
|
||||
form_id: String,
|
||||
file_name: String
|
||||
file_name: String,
|
||||
) -> Result<(), ServerFnError> {
|
||||
use crate::utils::server::create_aws_config;
|
||||
use super::S3_BUCKET_NAME;
|
||||
use crate::pages::api::tokens::validate_and_get_token_info;
|
||||
use crate::utils::server::create_aws_config;
|
||||
|
||||
let user_claims = validate_and_get_token_info(access_token).await?;
|
||||
let subject = user_claims.subject;
|
||||
@@ -76,7 +135,7 @@ pub async fn delete_file(
|
||||
|
||||
client
|
||||
.delete_object()
|
||||
.bucket("leptos-scholarhips")
|
||||
.bucket(S3_BUCKET_NAME)
|
||||
.key(key)
|
||||
.send()
|
||||
.await
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use leptos::prelude::*;
|
||||
use crate::components::FileDrop;
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn AdditionalPage() -> impl IntoView {
|
||||
@@ -10,7 +10,7 @@ pub fn AdditionalPage() -> impl IntoView {
|
||||
"This is just a placeholder for now. We'll show redirect buttons to each form."
|
||||
</div>
|
||||
<div class="text-lg">"Here's a testing file input!"</div>
|
||||
<FileDrop name="testing" form_id="student_additional"/>
|
||||
<FileDrop name="testing" form_id="student_additional" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user