feat: complete refactor of all data structures and fix serialization

This commit is contained in:
alphafetus committed 2025-09-26 12:09:38 -04:00
1 parent f2d8688c0c
commit c573263f48
4 files changed
+26 -34

No files matched your search

+5 -5
View File
@@ -15,19 +15,19 @@ use crate::components::ValueType;
/// now, the table will simply create a single massive `Map` field that contains all the stored /// now, the table will simply create a single massive `Map` field that contains all the stored
/// information in the `HashMap`. We'd prefer to be able to get single values if possible, /// information in the `HashMap`. We'd prefer to be able to get single values if possible,
/// especially for interoperability with other applications, should they be created. /// especially for interoperability with other applications, should they be created.
#[derive(Debug, Clone, Default, Serialize, Deserialize, Reactive)] #[derive(Debug, Clone, Serialize, Deserialize, Reactive)]
pub struct ExpandableInfo { pub struct ExpandableInfo {
/// The main key member. Required and used to get/put the information in a database. /// The main key member. Required and used to get/put the information in a database.
key_member: String, pub subject: String,
/// The main data store. Contains all information that this struct needs to hold. /// The main data store. Contains all information that this struct needs to hold.
#[serde(default)] #[serde(default, flatten)]
data: HashMap<String, ValueType>, pub data: HashMap<String, ValueType>,
} }
impl ExpandableInfo { impl ExpandableInfo {
pub fn new(key: String) -> Self { pub fn new(key: String) -> Self {
Self { Self {
key_member: key, subject: key,
data: HashMap::new(), data: HashMap::new(),
} }
} }
+3 -3
View File
@@ -9,7 +9,7 @@ pub enum ValueType {
/// Indicates a `String` value. /// Indicates a `String` value.
String(Option<String>), String(Option<String>),
/// Indicates an `i32` value. /// Indicates an `i32` value.
Number(Option<i32>), Number(Option<String>),
/// Indicates a `Vec<T>`, where `T` can be any other `ValueType`. /// Indicates a `Vec<T>`, where `T` can be any other `ValueType`.
List(Option<Vec<ValueType>>), List(Option<Vec<ValueType>>),
} }
@@ -35,7 +35,7 @@ impl ValueType {
} }
/// Attempts to get this value as an `i32`. /// Attempts to get this value as an `i32`.
pub fn as_number(&self) -> Result<Option<i32>, Self> { pub fn as_number(&self) -> Result<Option<String>, Self> {
if let ValueType::Number(v) = self { if let ValueType::Number(v) = self {
Ok(v.clone()) Ok(v.clone())
} else { } else {
@@ -69,7 +69,7 @@ impl Display for ValueType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self { let str = match self {
ValueType::String(s) => s.clone().unwrap_or("".to_owned()), ValueType::String(s) => s.clone().unwrap_or("".to_owned()),
ValueType::Number(n) => n.unwrap_or(0).to_string(), ValueType::Number(n) => n.clone().unwrap_or("".to_owned()),
ValueType::List(l) => format!("{l:?}"), ValueType::List(l) => format!("{l:?}"),
}; };
write!(f, "{}", str) write!(f, "{}", str)
+1 -1
View File
@@ -44,7 +44,7 @@ pub fn OutlinedTextField(
// If the number parses, Some(num), else None, but we want to keep the value that's // If the number parses, Some(num), else None, but we want to keep the value that's
// in the text field at this point, just set the error. // in the text field at this point, just set the error.
console_log(format!("Parsing {to_parse} to a number").as_str()); console_log(format!("Parsing {to_parse} to a number").as_str());
match to_parse.parse::<i32>().map(|n| ValueType::Number(Some(n))) { match to_parse.parse::<i32>().map(|n| ValueType::Number(Some(n.to_string()))) {
Ok(value_type) => { Ok(value_type) => {
error.set(false); error.set(false);
value.set(value_type.clone()); value.set(value_type.clone());
+17 -25
View File
@@ -7,12 +7,11 @@ use aws_sdk_dynamodb::{Client, error::ProvideErrorMetadata, types::AttributeValu
use serde_dynamo::{from_item, to_item}; use serde_dynamo::{from_item, to_item};
use crate::app::Unauthenticated; use crate::app::Unauthenticated;
use crate::common::{ExpandableInfo, StudentInfo, UserClaims}; use crate::common::{ExpandableInfo, UserClaims};
use crate::components::{ActionButton, CheckboxList, Loading, OutlinedTextField, Panel, RadioList, Row, Select, ValueType}; use crate::components::{ActionButton, CheckboxList, Loading, OutlinedTextField, Panel, RadioList, 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};
use reactive_stores::Store;
use traits::{AsReactive, ReactiveCapture}; use traits::{AsReactive, ReactiveCapture};
/// # Get Student Info /// # Get Student Info
@@ -20,18 +19,18 @@ use traits::{AsReactive, ReactiveCapture};
/// ///
/// All information is found by using a `GetItemCommand` in the student application DynamoDB table. /// All information is found by using a `GetItemCommand` in the student application DynamoDB table.
#[server(GetSubmission, endpoint = "/get-submission")] #[server(GetSubmission, endpoint = "/get-submission")]
pub async fn get_submission(id: String) -> Result<StudentInfo, ServerFnError> { pub async fn get_submission(subject: String) -> Result<ExpandableInfo, ServerFnError> {
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(format!("Getting values from API using username {}", id).as_str()); console_log(format!("Getting values from API using subject {}", subject).as_str());
// Gets the item from the database. It only returns an error if the database // Gets the item from the database. It only returns an error if the database
// experiences an error - not if the item is not found. // experiences an error - not if the item is not found.
match dbclient match dbclient
.get_item() .get_item()
.table_name("student-applications") .table_name("leptos-test")
.key("Email", AttributeValue::S(id.clone())) .key("subject", AttributeValue::S(subject.clone()))
.send() .send()
.await .await
{ {
@@ -41,8 +40,7 @@ pub async fn get_submission(id: String) -> Result<StudentInfo, ServerFnError> {
Ok(from_item(item)?) Ok(from_item(item)?)
} else { } else {
console_log("Couldn't find entry, returning default."); console_log("Couldn't find entry, returning default.");
let mut info = StudentInfo::default(); let info = ExpandableInfo::new(subject);
info.Email = id; // Set the correct subject
Ok(info) Ok(info)
} }
} }
@@ -69,7 +67,7 @@ pub async fn create_sample_submission(student_info: ExpandableInfo) -> Result<()
console_log(format!("Arguments for sample submission: {:?}", item).as_str()); console_log(format!("Arguments for sample submission: {:?}", item).as_str());
match dbclient match dbclient
.put_item() .put_item()
.table_name("student-applications") .table_name("leptos-test")
.set_item(Some(item)) .set_item(Some(item))
.send() .send()
.await .await
@@ -92,7 +90,7 @@ pub async fn create_sample_submission(student_info: ExpandableInfo) -> Result<()
pub async fn log_expandable(info: ExpandableInfo) -> Result<(), ServerFnError> { pub async fn log_expandable(info: ExpandableInfo) -> Result<(), ServerFnError> {
console_log(format!("Was given info: {:?}", info).as_str()); console_log(format!("Was given info: {:?}", info).as_str());
// Manually specify the type here since we're not able to infer the type later. // Manually specify the type here since we're not able to infer the type later.
let item: HashMap<String, AttributeValue> = to_item(&info)?; let item: HashMap<String, AttributeValue> = to_item(info)?;
console_log(format!("Converted into a DynamoDB item: {:?}", item).as_str()); console_log(format!("Converted into a DynamoDB item: {:?}", item).as_str());
Ok(()) Ok(())
@@ -115,14 +113,14 @@ pub fn HomePage() -> impl IntoView {
// Note that the value passed in MUST be equatable. // Note that the value passed in MUST be equatable.
// We get/unwrap the value repeatedly until we get a simple string value, then clone it so that // We get/unwrap the value repeatedly until we get a simple string value, then clone it so that
// we don't lose access to it in the future, should we need it again. // we don't lose access to it in the future, should we need it again.
let server_resource: Resource<StudentInfo> = Resource::new( let server_resource: Resource<ExpandableInfo> = 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(subject) => get_submission(subject.clone()).await.unwrap_or_else(|e| {
console_log(e.to_string().as_str()); console_log(e.to_string().as_str());
StudentInfo::default() ExpandableInfo::new(subject)
}), }),
None => StudentInfo::default(), None => ExpandableInfo::new("".to_owned()),
}, },
); );
let submit_action = ServerAction::<CreateSampleSubmission>::new(); let submit_action = ServerAction::<CreateSampleSubmission>::new();
@@ -137,13 +135,7 @@ pub fn HomePage() -> impl IntoView {
server_resource server_resource
.get() .get()
.map(|submission| { .map(|submission| {
let expandable = ExpandableInfo::new("email".into()); let expandable_react = submission.as_reactive();
let expandable_react = expandable.as_reactive();
expandable_react.data.update(|map| {
map.insert("email".into(), ValueType::String(Some("test@region15.org".into())));
map.insert("phone_number".into(), ValueType::String(Some("123456789-10".into())));
map.insert("math_sat".into(), ValueType::Number(Some(1234)));
});
let elements_disabled = RwSignal::new(false); let elements_disabled = RwSignal::new(false);
let result_msg = Signal::derive(move || { let result_msg = Signal::derive(move || {
// Eventually, this function will show and hide a loading symbol // Eventually, this function will show and hide a loading symbol
@@ -246,6 +238,7 @@ pub fn HomePage() -> impl IntoView {
/> />
</Row> </Row>
<Row> <Row>
// Checkbox { data_member, label, items }
<CheckboxList <CheckboxList
label="Favorite Candies:" label="Favorite Candies:"
items=vec!["Twizzlers", "Reese's", "Starburst"] items=vec!["Twizzlers", "Reese's", "Starburst"]
@@ -263,7 +256,7 @@ pub fn HomePage() -> impl IntoView {
label="Gender:" label="Gender:"
value_list=vec!["Male", "Female", "Prefer not to answer"] value_list=vec!["Male", "Female", "Prefer not to answer"]
.into_iter() .into_iter()
.map(|s| s.into()) .map(|s| s.to_owned())
.collect() .collect()
disabled=elements_disabled disabled=elements_disabled
data_member="gender" data_member="gender"
@@ -275,8 +268,7 @@ pub fn HomePage() -> impl IntoView {
on:click=move |_| { on:click=move |_| {
let captured_map = expandable_react.capture(); let captured_map = expandable_react.capture();
console_log(format!("Map values: {:?}", captured_map).as_str()); console_log(format!("Map values: {:?}", captured_map).as_str());
submit_action submit_action.dispatch(CreateSampleSubmission {
.dispatch(CreateSampleSubmission {
student_info: captured_map, student_info: captured_map,
}); });
} }