feat: add new admin APIs and applicants page

This commit is contained in:
alphafetus committed 2026-04-25 18:06:38 -04:00
1 parent 26b984904b
commit 3cc67cb9e6
9 files changed
+677 -46

No files matched your search

+3 -3
View File
@@ -66,7 +66,7 @@ pub async fn upload_file(data: MultipartData) -> Result<String, ServerFnError> {
return Err(ServerFnError::new("Missing file contents"));
}
let user_claims = validate_and_get_token_info(access_token).await?;
let user_claims = validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?;
let subject = user_claims.subject;
let key = format!("{form_id}/{subject}/{input_name}/{file_name}");
@@ -157,7 +157,7 @@ pub async fn delete_file(
) -> Result<String, ServerFnError> {
use imports::*;
let user_claims = validate_and_get_token_info(access_token).await?;
let user_claims = validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?;
let subject = user_claims.subject;
let entry_hk = format!("STUDENT#{subject}");
@@ -226,7 +226,7 @@ pub async fn list_files(
) -> Result<Vec<String>, ServerFnError> {
use imports::*;
let user_claims = validate_and_get_token_info(access_token).await?;
let user_claims = validate_and_get_token_info(access_token, "us-east-1_Lfjuy5zaM", "us-east-1").await?;
let subject = user_claims.subject;
let entry_hk = format!("STUDENT#{subject}");
+58 -19
View File
@@ -137,24 +137,41 @@ pub async fn get_all_student_data(
}
#[server]
pub async fn get_completed_students(
pub async fn provider_get_completed_students(
access_token: String,
) -> 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?;
if !claims.groups.contains(&"ScholarshipProviders".to_string()) {
return Err(ServerFnError::new(
"User is not in the ScholarshipProviders group",
));
}
get_completed_students().await
}
#[server]
pub async fn admin_get_completed_students(
access_token: String,
) -> Result<HashMap<String, HashMap<String, crate::common::ValueType>>, ServerFnError> {
use imports::*;
let _ = validate_and_get_token_info(access_token, "us-east-1_rvCU4Xy4j", "us-east-1").await?;
get_completed_students().await
}
#[server]
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
// the demographics form - everything else may bed from this.
// The easiest way is to get all the information and filter on this side, instead of bookkeeping
// on the database's side.
// We want to verify the access token first, and make sure that the user has the correct access.
let claims = validate_and_get_token_info(access_token).await?;
if !claims.groups.contains(&"ScholarshipProviders".to_string()) {
return Err(ServerFnError::new(
"User is not in the ScholarshipProviders group",
));
}
let client = create_dynamo_client().await;
let response = client
@@ -229,7 +246,7 @@ pub async fn get_file_by_key(
) -> Result<Vec<u8>, ServerFnError> {
use imports::*;
let user_claims = validate_and_get_token_info(access_token).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)
@@ -279,7 +296,7 @@ pub async fn get_student_files(
) -> Result<(String, Vec<u8>), ServerFnError> {
use imports::*;
let claims = validate_and_get_token_info(access_token).await?;
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()) {
@@ -420,19 +437,12 @@ pub async fn get_student_files(
///
/// This is only usable by users with provider-level access.
#[server]
pub async fn get_all_input_files(
access_token: String,
async fn get_all_input_files(
form_name: String,
input_name: String,
) -> Result<HashMap<String, Vec<String>>, ServerFnError> {
use imports::*;
let claims = validate_and_get_token_info(access_token).await?;
if !claims.groups.contains(&"ScholarshipProviders".to_string()) {
return Err(ServerFnError::new("Access denied: user is not a provider"));
}
let client = create_dynamo_client().await;
let res = client
@@ -473,3 +483,32 @@ pub async fn get_all_input_files(
Ok(result_map)
}
#[server]
pub async fn admin_get_all_input_files(
access_token: String,
form_name: String,
input_name: String,
) -> Result<HashMap<String, Vec<String>>, ServerFnError> {
use imports::*;
let _ = validate_and_get_token_info(access_token, "us-east-1_rvCU4Xy4j", "us-east-1").await?;
get_all_input_files(form_name, input_name).await
}
#[server]
pub async fn provider_get_all_input_files(
access_token: String,
form_name: String,
input_name: String,
) -> 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?;
if !claims.groups.contains(&"ScholarshipProviders".to_string()) {
return Err(ServerFnError::new("Access denied: user is not a provider"));
}
get_all_input_files(form_name, input_name).await
}
+11 -2
View File
@@ -17,10 +17,19 @@ pub async fn fetch_jwks(user_pool_id: String, region: String) -> Result<JwkSet,
/// Validates a JWT, and, if successful, decodes it into a series of user claims.
#[cfg(feature = "ssr")]
pub async fn validate_and_get_token_info(token: String) -> Result<UserClaims, ServerFnError> {
pub async fn validate_and_get_token_info(
token: String,
pool_id: impl Into<String>,
pool_region: impl Into<String>
) -> Result<UserClaims, ServerFnError> {
use jsonwebtoken::{Algorithm, Validation, decode};
let jwks = fetch_jwks("us-east-1_Lfjuy5zaM".into(), "us-east-1".into()).await?;
let pool_id = pool_id.into();
leptos::logging::debug_log!("Validating token using group {}", pool_id);
// let jwks = fetch_jwks("us-east-1_Lfjuy5zaM".into(), "us-east-1".into()).await?;
let jwks = fetch_jwks(pool_id, pool_region.into()).await?;
let validation = Validation::new(Algorithm::RS256);
// validation.set_audience(&["scholarships-rs"]);