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 60 additions and 48 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.
## Current Tasks
There are several tasks left to complete. These are a few of the larger ones:
- Build the application's custom elements
- Build out the database requests
- Create proper authentication
- PDF generation using data (server-side)
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.
+40 -28
View File
@@ -13,10 +13,10 @@ mod imports {
pub use aws_sdk_dynamodb::error::ProvideErrorMetadata;
pub use aws_sdk_dynamodb::types::AttributeValue;
pub use leptos::logging::{debug_log, error};
pub use std::collections::HashMap;
pub use zip::write::SimpleFileOptions;
pub use leptos::serde_json;
pub use std::collections::HashMap;
pub use std::process::Command;
pub use zip::write::SimpleFileOptions;
}
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> {
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()) {
return Err(ServerFnError::new(
"User is not in the ScholarshipProviders group",
@@ -169,7 +170,8 @@ pub async fn admin_get_completed_students(
}
#[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::*;
// 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> {
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
// user's subject)
@@ -301,12 +304,19 @@ pub async fn get_student_files(
) -> Result<(String, Vec<u8>), ServerFnError> {
use imports::*;
let claims = validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?;
// Check if the user has permission to get these files.
if !claims.groups.contains(&"ScholarshipProviders".to_string()) {
return Err(ServerFnError::new("Access denied: user is not a provider"));
}
match validate_and_get_token_info(access_token.clone(), "us-east-1_Lfjuy5zaM", "us-east-1")
.await
{
Ok(claims) => {
if !claims.groups.contains(&"ScholarshipProviders".to_string()) {
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;
@@ -510,7 +520,8 @@ pub async fn provider_get_all_input_files(
) -> Result<HashMap<String, Vec<String>>, ServerFnError> {
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()) {
return Err(ServerFnError::new("Access denied: user is not a provider"));
}
@@ -519,9 +530,7 @@ pub async fn provider_get_all_input_files(
}
#[server]
pub async fn get_student_pdf(
student_id: String,
) -> Result<(String, Vec<u8>), ServerFnError> {
pub async fn get_student_pdf(student_id: String) -> Result<(String, Vec<u8>), ServerFnError> {
use imports::*;
// 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 = 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())
.unwrap_or_default();
let last_name = student_info.get("last_name")
let last_name = student_info
.get("last_name")
.map(|v| v.to_string())
.unwrap_or_default();
@@ -543,7 +554,8 @@ pub async fn get_student_pdf(
.replace("[", "(")
.replace("]", ")")
// Ensures trailing commas for lists of dictionaries
.replace("))", "),)");
.replace("))", "),)")
.replace("\")", "\",)");
debug_log!("New string: {}", typst_string);
@@ -577,9 +589,7 @@ pub async fn get_student_pdf(
}
#[server]
pub async fn get_student_info_json(
student_id: String,
) -> Result<String, ServerFnError> {
pub async fn get_student_info_json(student_id: String) -> Result<String, ServerFnError> {
use imports::*;
let client = create_dynamo_client().await;
@@ -613,7 +623,8 @@ pub async fn get_student_info_json(
.map(|output| {
let items = output.items.unwrap_or_default();
debug_log!("Number of items: {}", items.len());
items.into_iter()
items
.into_iter()
.next()
.unwrap_or_default()
.get("extracurricular")
@@ -637,7 +648,8 @@ pub async fn get_student_info_json(
.map(|output| {
let items = output.items.unwrap_or_default();
debug_log!("Number of items: {}", items.len());
items.into_iter()
items
.into_iter()
.next()
.unwrap_or_default()
.get("extracurricular")
@@ -650,12 +662,12 @@ pub async fn get_student_info_json(
};
// Join items together into a single object.
let mut data = items.into_iter()
.fold(HashMap::new(), |mut map, item| {
item.into_iter()
.for_each(|(k, v)| { map.insert(k, v); });
map
let mut data = items.into_iter().fold(HashMap::new(), |mut map, item| {
item.into_iter().for_each(|(k, v)| {
map.insert(k, v);
});
map
});
data.insert("work_experience".to_string(), work_exp);
data.insert("extracurricular".to_string(), extracurricular);
+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::pages::UnauthenticatedPage;
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 leptos::ev::{Event, MouseEvent};
use leptos::html::Dialog;
@@ -68,12 +73,17 @@ fn ApplicantsScholarshipList() -> impl IntoView {
#[server]
async fn get_provider_scholarships(
access_token: String
access_token: String,
) -> Result<Vec<ExpandableInfo>, ServerFnError> {
use crate::pages::api::SCHOLARSHIPS_TABLE;
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.
let client = crate::utils::server::create_dynamo_client().await;
@@ -108,9 +118,7 @@ fn ApplicantsScholarshipList() -> impl IntoView {
let scholarships_res = Resource::new(
move || (trigger.track(), access_token.get()),
move |(_, access_token)| async move {
get_provider_scholarships(
access_token.unwrap_or_default()
).await
get_provider_scholarships(access_token.unwrap_or_default()).await
},
);
@@ -1121,9 +1129,8 @@ pub fn StudentInformationDialog(
let get_student_files = ServerAction::<GetStudentFiles>::new();
let get_student_pdf = ServerAction::<GetStudentPdf>::new();
let get_buttons_disabled = Memo::new(move |_| {
get_student_files.pending().get() && get_student_pdf.pending().get()
});
let get_buttons_disabled =
Memo::new(move |_| get_student_files.pending().get() && get_student_pdf.pending().get());
let on_click_fafsa = move |_| {
get_student_files.dispatch(GetStudentFiles {
access_token: access_token.get().unwrap_or_default(),
@@ -1310,10 +1317,7 @@ pub fn StudentInformationDialog(
})
})}
{move || Suspend::new(async move {
get_student_data(
student_id.get().unwrap_or_default(),
"family".to_string()
)
get_student_data(student_id.get().unwrap_or_default(), "family".to_string())
.await
.map(|student_info| {
view! {