Compare commits

..
5 Commits
Author SHA1 Message Date
alphafetus 9ff4b892f5 chore: update README 2026-10-25 14:10:56 -04:00
Lucas Vas 2e0e47d72b Merge pull request #81 from R15-Software-Dev/fix/admin-file-errors
Insert trailing commas after lists of strings
2026-04-27 15:02:16 -04:00
alphafetus 287598bb79 fix: insert trailing commas after lists of strings
This was already present for lists of dictionaries, but lists that contained a single string also failed to parse properly.
2026-04-27 14:57:31 -04:00
Lucas Vas fff1854067 Merge pull request #80 from R15-Software-Dev/fix/admin-file-errors
Allow essays and fafsa to be requested by administrators
2026-04-27 14:24:20 -04:00
alphafetus 6d6862a680 fix: allow essays and fafsa to be requested by administrators 2026-04-27 14:08:14 -04:00
3 changed files with 56 additions and 44 deletions

No files matched your search

+2 -6
View File
@@ -13,9 +13,5 @@ from the AWS resources, you will need to be approved by me or someone else with
usable auth key from AWS IAM. usable auth key from AWS IAM.
## Current Tasks ## Current Tasks
There are several tasks left to complete. These are a few of the larger ones: The application is deployed! However, it doesn't have some core features that need to be present in order for this
to be considered complete. Check the Issues tab for more information.
- Build the application's custom elements
- Build out the database requests
- Create proper authentication
- PDF generation using data (server-side)
+36 -24
View File
@@ -13,10 +13,10 @@ mod imports {
pub use aws_sdk_dynamodb::error::ProvideErrorMetadata; pub use aws_sdk_dynamodb::error::ProvideErrorMetadata;
pub use aws_sdk_dynamodb::types::AttributeValue; pub use aws_sdk_dynamodb::types::AttributeValue;
pub use leptos::logging::{debug_log, error}; pub use leptos::logging::{debug_log, error};
pub use std::collections::HashMap;
pub use zip::write::SimpleFileOptions;
pub use leptos::serde_json; pub use leptos::serde_json;
pub use std::collections::HashMap;
pub use std::process::Command; pub use std::process::Command;
pub use zip::write::SimpleFileOptions;
} }
static PDF_TEMPLATE: &str = include_str!("../../../pdf_template.typ"); static PDF_TEMPLATE: &str = include_str!("../../../pdf_template.typ");
@@ -147,7 +147,8 @@ pub async fn provider_get_completed_students(
) -> Result<HashMap<String, HashMap<String, crate::common::ValueType>>, ServerFnError> { ) -> Result<HashMap<String, HashMap<String, crate::common::ValueType>>, ServerFnError> {
use imports::*; use imports::*;
let claims = validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?; let claims =
validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?;
if !claims.groups.contains(&"ScholarshipProviders".to_string()) { if !claims.groups.contains(&"ScholarshipProviders".to_string()) {
return Err(ServerFnError::new( return Err(ServerFnError::new(
"User is not in the ScholarshipProviders group", "User is not in the ScholarshipProviders group",
@@ -169,7 +170,8 @@ pub async fn admin_get_completed_students(
} }
#[server] #[server]
async fn get_completed_students() -> Result<HashMap<String, HashMap<String, crate::common::ValueType>>, ServerFnError> { async fn get_completed_students()
-> Result<HashMap<String, HashMap<String, crate::common::ValueType>>, ServerFnError> {
use imports::*; use imports::*;
// We want to get all student information. The requirements are that the students have completed // We want to get all student information. The requirements are that the students have completed
@@ -251,7 +253,8 @@ pub async fn get_file_by_key(
) -> Result<Vec<u8>, ServerFnError> { ) -> Result<Vec<u8>, ServerFnError> {
use imports::*; use imports::*;
let user_claims = validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?; let user_claims =
validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?;
// Check if the user's subject is contained in the file_key (since all files are keyed by the // Check if the user's subject is contained in the file_key (since all files are keyed by the
// user's subject) // user's subject)
@@ -301,12 +304,19 @@ pub async fn get_student_files(
) -> Result<(String, Vec<u8>), ServerFnError> { ) -> Result<(String, Vec<u8>), ServerFnError> {
use imports::*; use imports::*;
let claims = validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?; match validate_and_get_token_info(access_token.clone(), "us-east-1_Lfjuy5zaM", "us-east-1")
.await
// Check if the user has permission to get these files. {
Ok(claims) => {
if !claims.groups.contains(&"ScholarshipProviders".to_string()) { if !claims.groups.contains(&"ScholarshipProviders".to_string()) {
return Err(ServerFnError::new("Access denied: user is not a provider")); return Err(ServerFnError::new("Access denied: user is not a provider"));
} }
}
Err(_) => {
let _ = validate_and_get_token_info(access_token, "us-east-1_rvCU4Xy4j", "us-east-1")
.await?;
}
};
let client = create_dynamo_client().await; let client = create_dynamo_client().await;
@@ -510,7 +520,8 @@ pub async fn provider_get_all_input_files(
) -> Result<HashMap<String, Vec<String>>, ServerFnError> { ) -> Result<HashMap<String, Vec<String>>, ServerFnError> {
use imports::*; use imports::*;
let claims = validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?; let claims =
validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?;
if !claims.groups.contains(&"ScholarshipProviders".to_string()) { if !claims.groups.contains(&"ScholarshipProviders".to_string()) {
return Err(ServerFnError::new("Access denied: user is not a provider")); return Err(ServerFnError::new("Access denied: user is not a provider"));
} }
@@ -519,9 +530,7 @@ pub async fn provider_get_all_input_files(
} }
#[server] #[server]
pub async fn get_student_pdf( pub async fn get_student_pdf(student_id: String) -> Result<(String, Vec<u8>), ServerFnError> {
student_id: String,
) -> Result<(String, Vec<u8>), ServerFnError> {
use imports::*; use imports::*;
// NOTE: this API requires that the server has the typst-cli available ON PATH. // NOTE: this API requires that the server has the typst-cli available ON PATH.
@@ -529,11 +538,13 @@ pub async fn get_student_pdf(
let student_info_str = get_student_info_json(student_id.clone()).await?; let student_info_str = get_student_info_json(student_id.clone()).await?;
let student_info = get_student_data(student_id, "DEMOGRAPHICS".to_string()).await?; let student_info = get_student_data(student_id, "DEMOGRAPHICS".to_string()).await?;
let first_name = student_info.get("first_name") let first_name = student_info
.get("first_name")
.map(|v| v.to_string()) .map(|v| v.to_string())
.unwrap_or_default(); .unwrap_or_default();
let last_name = student_info.get("last_name") let last_name = student_info
.get("last_name")
.map(|v| v.to_string()) .map(|v| v.to_string())
.unwrap_or_default(); .unwrap_or_default();
@@ -543,7 +554,8 @@ pub async fn get_student_pdf(
.replace("[", "(") .replace("[", "(")
.replace("]", ")") .replace("]", ")")
// Ensures trailing commas for lists of dictionaries // Ensures trailing commas for lists of dictionaries
.replace("))", "),)"); .replace("))", "),)")
.replace("\")", "\",)");
debug_log!("New string: {}", typst_string); debug_log!("New string: {}", typst_string);
@@ -577,9 +589,7 @@ pub async fn get_student_pdf(
} }
#[server] #[server]
pub async fn get_student_info_json( pub async fn get_student_info_json(student_id: String) -> Result<String, ServerFnError> {
student_id: String,
) -> Result<String, ServerFnError> {
use imports::*; use imports::*;
let client = create_dynamo_client().await; let client = create_dynamo_client().await;
@@ -613,7 +623,8 @@ pub async fn get_student_info_json(
.map(|output| { .map(|output| {
let items = output.items.unwrap_or_default(); let items = output.items.unwrap_or_default();
debug_log!("Number of items: {}", items.len()); debug_log!("Number of items: {}", items.len());
items.into_iter() items
.into_iter()
.next() .next()
.unwrap_or_default() .unwrap_or_default()
.get("extracurricular") .get("extracurricular")
@@ -637,7 +648,8 @@ pub async fn get_student_info_json(
.map(|output| { .map(|output| {
let items = output.items.unwrap_or_default(); let items = output.items.unwrap_or_default();
debug_log!("Number of items: {}", items.len()); debug_log!("Number of items: {}", items.len());
items.into_iter() items
.into_iter()
.next() .next()
.unwrap_or_default() .unwrap_or_default()
.get("extracurricular") .get("extracurricular")
@@ -650,10 +662,10 @@ pub async fn get_student_info_json(
}; };
// Join items together into a single object. // Join items together into a single object.
let mut data = items.into_iter() let mut data = items.into_iter().fold(HashMap::new(), |mut map, item| {
.fold(HashMap::new(), |mut map, item| { item.into_iter().for_each(|(k, v)| {
item.into_iter() map.insert(k, v);
.for_each(|(k, v)| { map.insert(k, v); }); });
map map
}); });
+18 -14
View File
@@ -1,8 +1,13 @@
use crate::common::{ComparisonData, ExpandableInfo, SchemaContainerStyle, SchemaHeaderStyle, SchemaNode, SchemaType, ValueType}; use crate::common::{
ComparisonData, ExpandableInfo, SchemaContainerStyle, SchemaHeaderStyle, SchemaNode,
SchemaType, ValueType,
};
use crate::components::{ActionButton, Banner, DataDisplay, Loading}; use crate::components::{ActionButton, Banner, DataDisplay, Loading};
use crate::pages::UnauthenticatedPage; use crate::pages::UnauthenticatedPage;
use crate::pages::api::get_comparison_info; use crate::pages::api::get_comparison_info;
use crate::pages::api::students::{GetStudentFiles, provider_get_all_input_files, get_student_data, GetStudentPdf}; use crate::pages::api::students::{
GetStudentFiles, GetStudentPdf, get_student_data, provider_get_all_input_files,
};
use base64::Engine; use base64::Engine;
use leptos::ev::{Event, MouseEvent}; use leptos::ev::{Event, MouseEvent};
use leptos::html::Dialog; use leptos::html::Dialog;
@@ -68,12 +73,17 @@ fn ApplicantsScholarshipList() -> impl IntoView {
#[server] #[server]
async fn get_provider_scholarships( async fn get_provider_scholarships(
access_token: String access_token: String,
) -> Result<Vec<ExpandableInfo>, ServerFnError> { ) -> Result<Vec<ExpandableInfo>, ServerFnError> {
use crate::pages::api::SCHOLARSHIPS_TABLE; use crate::pages::api::SCHOLARSHIPS_TABLE;
use crate::pages::api::tokens::validate_and_get_token_info; use crate::pages::api::tokens::validate_and_get_token_info;
let claims = validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM".to_string(), "us-east-1".to_string()).await?; let claims = validate_and_get_token_info(
access_token,
"us-east-1_Lfjuy5zaM".to_string(),
"us-east-1".to_string(),
)
.await?;
// Get the information from the database. // Get the information from the database.
let client = crate::utils::server::create_dynamo_client().await; let client = crate::utils::server::create_dynamo_client().await;
@@ -108,9 +118,7 @@ fn ApplicantsScholarshipList() -> impl IntoView {
let scholarships_res = Resource::new( let scholarships_res = Resource::new(
move || (trigger.track(), access_token.get()), move || (trigger.track(), access_token.get()),
move |(_, access_token)| async move { move |(_, access_token)| async move {
get_provider_scholarships( get_provider_scholarships(access_token.unwrap_or_default()).await
access_token.unwrap_or_default()
).await
}, },
); );
@@ -1121,9 +1129,8 @@ pub fn StudentInformationDialog(
let get_student_files = ServerAction::<GetStudentFiles>::new(); let get_student_files = ServerAction::<GetStudentFiles>::new();
let get_student_pdf = ServerAction::<GetStudentPdf>::new(); let get_student_pdf = ServerAction::<GetStudentPdf>::new();
let get_buttons_disabled = Memo::new(move |_| { let get_buttons_disabled =
get_student_files.pending().get() && get_student_pdf.pending().get() Memo::new(move |_| get_student_files.pending().get() && get_student_pdf.pending().get());
});
let on_click_fafsa = move |_| { let on_click_fafsa = move |_| {
get_student_files.dispatch(GetStudentFiles { get_student_files.dispatch(GetStudentFiles {
access_token: access_token.get().unwrap_or_default(), access_token: access_token.get().unwrap_or_default(),
@@ -1310,10 +1317,7 @@ pub fn StudentInformationDialog(
}) })
})} })}
{move || Suspend::new(async move { {move || Suspend::new(async move {
get_student_data( get_student_data(student_id.get().unwrap_or_default(), "family".to_string())
student_id.get().unwrap_or_default(),
"family".to_string()
)
.await .await
.map(|student_info| { .map(|student_info| {
view! { view! {