feat: implement serde_dynamo for all possible functions

This commit is contained in:
alphafetus committed 2025-08-29 09:21:33 -04:00
1 parent 1595553cc5
commit 42d2041a4f
4 files changed
+68 -88

No files matched your search

Generated
+12
View File
@@ -2958,6 +2958,7 @@ dependencies = [
"leptos_router",
"macros",
"serde",
"serde_dynamo",
"tokio",
"url",
"uuid",
@@ -3051,6 +3052,17 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_dynamo"
version = "4.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e36c1b1792cfd9de29eb373ee6a4b74650369c096f55db7198ceb7b8921d1f7f"
dependencies = [
"aws-sdk-dynamodb",
"base64 0.21.7",
"serde",
]
[[package]]
name = "serde_json"
version = "1.0.140"
+3 -1
View File
@@ -15,9 +15,10 @@ leptos_axum = { version = "0.8", optional = true }
leptos_meta = { version = "0.8" }
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
wasm-bindgen = { version = "=0.2.100", optional = true }
aws-sdk-dynamodb = { version = "1.74.0", optional = true }
aws-sdk-dynamodb = { version = "1", optional = true }
aws-config = { version = "1.6.2", optional = true }
serde = "1.0.219"
serde_dynamo = { version = "4.2.14", features = ["aws-sdk-dynamodb+1"], optional = true }
uuid = { version = "1.17.0", features = ["serde", "v4", "js"] }
leptos_oidc = "0.9.0"
url = "2.5.4"
@@ -36,6 +37,7 @@ ssr = [
"dep:leptos_axum",
"dep:aws-sdk-dynamodb",
"dep:aws-config",
"dep:serde_dynamo",
"leptos/ssr",
"leptos_meta/ssr",
"leptos_router/ssr",
+18 -5
View File
@@ -1,10 +1,23 @@
use macros::Reactive;
use serde::{Deserialize, Serialize};
use std::default::Default;
#[derive(Serialize, Deserialize, Debug, Clone, Reactive)]
/// Represents information about a student.
#[derive(Serialize, Deserialize, Debug, Clone, Reactive, Default)]
pub struct StudentInfo {
pub first_name: String,
pub last_name: String,
pub math_sat_score: i32,
pub student_email: String,
/// The email address of the student. This was used in the previous version of the database,
/// and should now be deprecated.
pub Email: String,
/// The student's first name.
#[serde(default)]
pub studentFirstName: String,
/// The student's last name.
#[serde(default)]
pub studentLastName: String,
/// The student's highest math score on the SAT.
#[serde(default)]
pub mathScoreSAT: i32,
/// The student's desired contact email.
#[serde(default)]
pub studentEmail: String,
}
+35 -82
View File
@@ -1,8 +1,15 @@
// Server dependencies
#[cfg(feature = "ssr")]
use aws_sdk_dynamodb::{error::ProvideErrorMetadata, types::AttributeValue, Client};
#[cfg(feature = "ssr")]
use serde_dynamo::{from_item, to_item};
use crate::app::Unauthenticated;
use crate::common::{StudentInfo, StudentInfoReactive, UserClaims};
use crate::components::{ActionButton, Loading, OutlinedTextField, Select, CheckboxList, Panel, Row};
use crate::components::{
ActionButton, CheckboxList, Loading, OutlinedTextField, Panel, Row, Select,
};
use leptos::leptos_dom::logging::console_log;
use leptos::prelude::*;
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
@@ -24,38 +31,11 @@ pub async fn get_submission(id: String) -> Result<StudentInfo, ServerFnError> {
Ok(output) => {
let item = output.item.unwrap();
let first_name: String = item
.get("studentFirstName")
.and_then(|attr| attr.as_s().ok())
.map(|s| s.to_owned())
.unwrap_or_default();
let info: StudentInfo = from_item(item).unwrap();
let last_name: String = item
.get("studentLastName")
.and_then(|attr| attr.as_s().ok())
.map(|s| s.to_owned())
.unwrap_or_default();
console_log(format!("Got info from API: {:?}", info).as_str());
let math_sat_score = item
.get("mathSAT")
.and_then(|attr| attr.as_n().ok())
.map(|n| n.parse::<i32>().unwrap_or(0))
.unwrap_or_default();
let student_email = item
.get("studentEmail")
.and_then(|attr| attr.as_s().ok())
.map(|s| s.to_owned())
.unwrap_or_default();
console_log(format!("Got values from API: {} {}", first_name, last_name).as_str());
Ok(StudentInfo {
first_name,
last_name,
math_sat_score,
student_email,
})
Ok(info)
}
Err(err) => {
let msg = err.message().unwrap_or("Unknown error");
@@ -69,45 +49,19 @@ pub async fn create_sample_submission(
student_info: StudentInfo,
subject: String,
) -> Result<(), ServerFnError> {
use aws_sdk_dynamodb::{types::AttributeValue, Client};
use aws_sdk_dynamodb::Client;
let config = aws_config::load_defaults(aws_config::BehaviorVersion::latest()).await;
let dbclient = Client::new(&config);
console_log(
format!(
"Creating sample submission with name {} {}, math score {}, and subject {}",
student_info.first_name, student_info.last_name, student_info.math_sat_score, subject
)
.as_str(),
);
// The put_item action can create or update an item in a DynamoDB table.
// It will completely replace any existing item with the same primary key,
// which is the desired behavior.
let item = to_item(student_info).unwrap();
match dbclient
.update_item()
.table_name("student-applications")
.key("Email", AttributeValue::S(subject))
.expression_attribute_values(
":studentFirstName",
AttributeValue::S(student_info.first_name),
)
.expression_attribute_values(
":studentLastName",
AttributeValue::S(student_info.last_name),
)
.expression_attribute_values(
":mathScoreSAT",
AttributeValue::N(student_info.math_sat_score.to_string()),
)
.expression_attribute_names("#studentFirstName", "studentFirstName")
.expression_attribute_names("#studentLastName", "studentLastName")
.expression_attribute_names("#mathScoreSAT", "mathScoreSAT")
.update_expression(
concat!("SET #studentFirstName = :studentFirstName, ",
"#studentLastName = :studentLastName, ",
"#mathScoreSAT = :mathScoreSAT"),
)
.send()
.await
.put_item()
.set_item(Some(item))
.send().await
{
Ok(_) => Ok(()),
Err(err) => Err(ServerFnError::new(err.into_service_error().to_string())),
@@ -133,21 +87,14 @@ pub fn HomePage() -> impl IntoView {
let server_resource = Resource::new(
move || user_claims.get().map(|claim| claim.claims.subject.clone()),
async |opt_username| match opt_username {
Some(username) => get_submission(username).await.unwrap_or_else(|e| {
Some(username) => get_submission(username)
.await
.unwrap_or_else(|e| {
console_log(e.to_string().as_str());
StudentInfo {
first_name: String::from("Error"),
last_name: String::new(),
math_sat_score: 0,
student_email: String::new()
StudentInfo::default()
}
}),
None => StudentInfo {
first_name: String::new(),
last_name: String::new(),
math_sat_score: 0,
student_email: String::new()
},
),
None => StudentInfo::default()
},
);
let submit_action = ServerAction::<CreateSampleSubmission>::new();
@@ -169,15 +116,21 @@ pub fn HomePage() -> impl IntoView {
view! {
<div class="flex flex-row">
<Panel>
<Row>
<p>
"Testing paragraph! This panel should be the same size as the other."
</p>
</Row>
<Row>
<p>"Current user's subject ID: "{reactive_info.Email}</p>
</Row>
</Panel>
<Panel>
<Row>
<p>
"Current user's reported full name from the API: "
{reactive_info.first_name}" "{reactive_info.last_name}
{reactive_info.studentFirstName}" "
{reactive_info.studentLastName}
</p>
</Row>
<Row>
@@ -185,19 +138,19 @@ pub fn HomePage() -> impl IntoView {
label="First Name:"
placeholder="John"
disabled=elements_disabled
value=reactive_info.first_name
value=reactive_info.studentFirstName
/>
<OutlinedTextField
label="Last Name:"
placeholder="Smith"
disabled=elements_disabled
value=reactive_info.last_name
value=reactive_info.studentLastName
/>
<OutlinedTextField
label="Contact Email"
placeholder="temp@region15.org"
disabled=elements_disabled
value=reactive_info.student_email
value=reactive_info.studentEmail
/>
</Row>
<Row>