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