#[cfg(feature = "ssr")] mod imports { pub use super::super::SCHOLARSHIPS_TABLE; pub use crate::common::ValueType; pub use crate::utils::server::create_dynamo_client; pub use aws_sdk_dynamodb::{error::ProvideErrorMetadata, types::AttributeValue}; pub use leptos::logging::{debug_log, error, log}; pub use uuid::Uuid; } use crate::common::ExpandableInfo; use leptos::prelude::ServerFnError; use leptos::server; use leptos::server_fn::codec::Json; #[server(GetScholarshipInfo)] pub async fn get_scholarship_info(id: String) -> Result { use imports::*; let client = create_dynamo_client().await; // Perform the operation - we just want to return all data that's contained in this entry, // or just return an empty ExpandableInfo struct. log!("Getting scholarship info using id {:?}", id); match client .get_item() .table_name(SCHOLARSHIPS_TABLE) .key("subject", AttributeValue::S(id.clone())) .send() .await { Ok(output) => { if let Some(item) = output.item { log!("Found output from API: {:?}", item); Ok(serde_dynamo::from_item(item)?) } else { log!("Couldn't find scholarship with ID {:?}", id); Err(ServerFnError::new( "Couldn't find scholarship with given ID.", )) } } Err(err) => { let msg = err.message().unwrap_or("An unknown error occurred"); error!("{}", msg); Err(ServerFnError::new(msg)) } } } #[server(CreateScholarshipInfo, input = Json)] pub async fn create_scholarship_info(info: ExpandableInfo) -> Result<(), ServerFnError> { use aws_sdk_dynamodb::error::ProvideErrorMetadata; use imports::*; let client = create_dynamo_client().await; log!( "Creating or updating scholarship with ID {:?}", info.subject ); match client .put_item() .table_name(SCHOLARSHIPS_TABLE) .set_item(Some(serde_dynamo::to_item(&info)?)) .send() .await { Ok(_) => Ok(()), Err(err) => { let msg = err.message().unwrap_or("An unknown error occurred"); error!("{}", msg); Err(ServerFnError::new(msg)) } } } #[server(GetAllScholarshipInfo)] pub async fn get_all_scholarship_info() -> Result, ServerFnError> { use imports::*; let client = create_dynamo_client().await; debug_log!("Getting all scholarship info"); match client.scan().table_name(SCHOLARSHIPS_TABLE).send().await { Ok(output) => { if let Some(items) = output.items { Ok(serde_dynamo::from_items(items)?) } else { Ok(Vec::new()) } } Err(err) => { let msg = err.message().unwrap_or("Unknown error occurred"); error!("{}", msg); Err(ServerFnError::new(msg)) } } } /// Gets all scholarships that are associated with the given scholarship provider's ID. #[server(GetProviderScholarships)] pub async fn get_provider_scholarships( provider_id: String, ) -> Result, ServerFnError> { use imports::*; let client = create_dynamo_client().await; debug_log!( "Getting provider scholarships for provider with ID {:?}", provider_id ); match client .scan() .table_name(SCHOLARSHIPS_TABLE) .expression_attribute_values( ":id", serde_dynamo::to_attribute_value(ValueType::String(Some(provider_id)))?, ) .filter_expression("provider_id = :id") .send() .await { Ok(output) => { if let Some(items) = output.items { Ok(serde_dynamo::from_items(items)?) } else { Ok(Vec::new()) } } Err(err) => { let msg = err.message().unwrap_or("Unknown error occurred"); error!("{}", msg); Err(ServerFnError::new(msg)) } } } /// Creates a new scholarship with a unique ID, and then returns that ID. #[server(RegisterScholarship)] pub async fn register_scholarship(provider_id: String) -> Result { use imports::*; let client = create_dynamo_client().await; debug_log!( "Creating scholarship for provider with ID {:?}", provider_id ); let mut current_uuid = Uuid::new_v4().to_string(); let mut item = ExpandableInfo::new(current_uuid.clone()); item.data.insert( "provider_id".to_string(), ValueType::String(Some(provider_id)), ); loop { let ser_item = serde_dynamo::to_item(&item)?; match client .put_item() .table_name(SCHOLARSHIPS_TABLE) .set_item(Some(ser_item)) .condition_expression("attribute_not_exists(subject)") .send() .await { Ok(_) => { // Return the uuid that we used. return Ok(current_uuid); } Err(err) => { // Check the error to figure out if it was a key validation error. // If so, retry with new values. match err.code() { Some("ConditionalCheckFailedException") => { log!("Retrying conditional check."); log!("Current error: {:?}", err); current_uuid = Uuid::new_v4().to_string(); item.subject = current_uuid.clone(); continue; } _ => { return Err(ServerFnError::new( err.message().unwrap_or("Unknown error occurred"), )); } } } } } } /// Deletes a provider's scholarship given their provider ID and scholarship ID. #[server(DeleteProviderScholarship)] pub async fn delete_provider_scholarship( provider_id: String, scholarship_id: String, ) -> Result<(), ServerFnError> { use imports::*; let client = create_dynamo_client().await; debug_log!( "Deleting scholarship with ID {:?} for provider with ID {:?}", scholarship_id, provider_id ); // When we delete a scholarship, we need to ensure that the provider's ID matches the scholarship, // otherwise everyone can delete anyone else's scholarships. match client .delete_item() .table_name(SCHOLARSHIPS_TABLE) .key("subject", AttributeValue::S(scholarship_id)) .expression_attribute_values( ":provider_id", serde_dynamo::to_attribute_value(ValueType::String(Some(provider_id)))?, ) .condition_expression("provider_id = :provider_id") .send() .await { Ok(_) => Ok(()), Err(err) => { let msg = err.message().unwrap_or("Unknown error occurred"); error!("{}", msg); Err(ServerFnError::new(msg)) } } } #[server] pub async fn get_all_scholarships() -> Result>, ServerFnError> { use imports::*; use std::collections::HashMap; let client = create_dynamo_client().await; client .scan() .table_name(SCHOLARSHIPS_TABLE) .send() .await .map(|output| { let Some(items) = output.items else { return Vec::new(); }; items .iter() .map(|item| { // Each item should be simply converted from an AttributeValue to a ValueType. item.iter() .map(|(k, v)| (k.clone(), ValueType::from(v))) .collect::>() }) .collect::>() }) .map_err(|err| { let msg = err.message().unwrap_or("Unknown error occurred"); error!("{}", msg); ServerFnError::new(msg) }) }