Merge pull request #80 from R15-Software-Dev/fix/admin-file-errors

Allow essays and fafsa to be requested by administrators
This commit was merged in pull request #80.
This commit is contained in:
Lucas Vas authored and GitHub committed 2026-04-27 14:24:20 -04:00
commit fff1854067
2 files changed
+56 -41

No files matched your search

+38 -27
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. {
if !claims.groups.contains(&"ScholarshipProviders".to_string()) { Ok(claims) => {
return Err(ServerFnError::new("Access denied: user is not a provider")); 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; 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();
@@ -577,9 +588,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 +622,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 +647,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,12 +661,12 @@ 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
});
data.insert("work_experience".to_string(), work_exp); data.insert("work_experience".to_string(), work_exp);
data.insert("extracurricular".to_string(), extracurricular); 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::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! {