Implement template student portal #62
No files matched your search
Generated
+22
@@ -829,6 +829,27 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csv"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
|
||||||
|
dependencies = [
|
||||||
|
"csv-core",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "csv-core"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "darling"
|
name = "darling"
|
||||||
version = "0.20.11"
|
version = "0.20.11"
|
||||||
@@ -3027,6 +3048,7 @@ dependencies = [
|
|||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"chrono",
|
"chrono",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
|
"csv",
|
||||||
"icondata",
|
"icondata",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"leptos",
|
"leptos",
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ traits = { path = "./traits" }
|
|||||||
icondata = { version = "0.7.0", features = ["font-awesome"] }
|
icondata = { version = "0.7.0", features = ["font-awesome"] }
|
||||||
leptos_icons = "0.7.1"
|
leptos_icons = "0.7.1"
|
||||||
indexmap = { version = "2.13.0", features = ["serde"] }
|
indexmap = { version = "2.13.0", features = ["serde"] }
|
||||||
|
csv = "1.4.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["ssr"]
|
default = ["ssr"]
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ pub fn AppWithRoutes() -> impl IntoView {
|
|||||||
<ProviderRoutes />
|
<ProviderRoutes />
|
||||||
<LoanerRoutes />
|
<LoanerRoutes />
|
||||||
<StudentRoutes />
|
<StudentRoutes />
|
||||||
|
<AdminRoutes />
|
||||||
</Routes>
|
</Routes>
|
||||||
</ToastList>
|
</ToastList>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
+41
-116
@@ -1,17 +1,21 @@
|
|||||||
use chrono::DateTime;
|
use crate::common::{DateInfo, DateRange};
|
||||||
use crate::components::ActionButton;
|
use crate::components::ActionButton;
|
||||||
|
use crate::pages::api::exports::get_scholarship_csv;
|
||||||
|
use crate::pages::api::{CreateDates, CreateTestComparisons, exports::GetScholarshipCsv};
|
||||||
|
use chrono::DateTime;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use crate::common::{DateInfo, DateRange};
|
use leptos::task::spawn_local;
|
||||||
use crate::pages::api::{CreateDates, CreateTestComparisons};
|
use leptos::wasm_bindgen::JsCast;
|
||||||
|
use leptos::web_sys::{Blob, HtmlAnchorElement, Url, js_sys};
|
||||||
|
|
||||||
fn get_important_dates() -> Vec<DateInfo> {
|
fn get_important_dates() -> Vec<DateInfo> {
|
||||||
vec! [
|
vec![
|
||||||
DateInfo {
|
DateInfo {
|
||||||
id: uuid::Uuid::new_v4().to_string(),
|
id: uuid::Uuid::new_v4().to_string(),
|
||||||
title: "Provider Forms Open".to_string(),
|
title: "Provider Forms Open".to_string(),
|
||||||
date: DateRange::Range(
|
date: DateRange::Range(
|
||||||
DateTime::parse_from_rfc3339("2026-01-20T07:15:00-05:00").unwrap(),
|
DateTime::parse_from_rfc3339("2026-01-20T07:15:00-05:00").unwrap(),
|
||||||
DateTime::parse_from_rfc3339("2026-03-03T23:59:00-05:00").unwrap()
|
DateTime::parse_from_rfc3339("2026-03-03T23:59:00-05:00").unwrap(),
|
||||||
),
|
),
|
||||||
description: "".to_string(),
|
description: "".to_string(),
|
||||||
},
|
},
|
||||||
@@ -20,7 +24,7 @@ fn get_important_dates() -> Vec<DateInfo> {
|
|||||||
title: "Student Forms Open".to_string(),
|
title: "Student Forms Open".to_string(),
|
||||||
date: DateRange::Range(
|
date: DateRange::Range(
|
||||||
DateTime::parse_from_rfc3339("2026-03-03T07:15:00-05:00").unwrap(),
|
DateTime::parse_from_rfc3339("2026-03-03T07:15:00-05:00").unwrap(),
|
||||||
DateTime::parse_from_rfc3339("2026-03-27T14:05:00-05:00").unwrap()
|
DateTime::parse_from_rfc3339("2026-03-27T14:05:00-05:00").unwrap(),
|
||||||
),
|
),
|
||||||
description: "".to_string(),
|
description: "".to_string(),
|
||||||
},
|
},
|
||||||
@@ -29,7 +33,7 @@ fn get_important_dates() -> Vec<DateInfo> {
|
|||||||
title: "Scholarship Decision Window".to_string(),
|
title: "Scholarship Decision Window".to_string(),
|
||||||
date: DateRange::Range(
|
date: DateRange::Range(
|
||||||
DateTime::parse_from_rfc3339("2026-04-10T07:15:00-05:00").unwrap(),
|
DateTime::parse_from_rfc3339("2026-04-10T07:15:00-05:00").unwrap(),
|
||||||
DateTime::parse_from_rfc3339("2026-05-01T23:59:00-05:00").unwrap()
|
DateTime::parse_from_rfc3339("2026-05-01T23:59:00-05:00").unwrap(),
|
||||||
),
|
),
|
||||||
description: "Start date may change if available sooner.".to_string(),
|
description: "Start date may change if available sooner.".to_string(),
|
||||||
},
|
},
|
||||||
@@ -52,127 +56,48 @@ fn get_important_dates() -> Vec<DateInfo> {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server]
|
|
||||||
async fn create_scholarship_export() -> Result<Vec<u8>, ServerFnError> {
|
|
||||||
// Exports olarship information - specifically the name of the scholarship, the name of the
|
|
||||||
// provider, and the scholarship's requirements.
|
|
||||||
// This means we'll need to get all the relations, all the scholarships, and all the contact info.
|
|
||||||
use crate::utils::server::create_dynamo_client;
|
|
||||||
use aws_sdk_dynamodb::client::Client;
|
|
||||||
use crate::pages::api::{get_comparison_info, SCHOLARSHIPS_TABLE, PROVIDER_CONTACT_TABLE};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use crate::common::ValueType;
|
|
||||||
|
|
||||||
let scan_table_items = async |client: &Client, table_name: &str| {
|
|
||||||
client.scan()
|
|
||||||
.table_name(table_name)
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.ok().and_then(|res| res.items)
|
|
||||||
.unwrap_or_default()
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug)]
|
|
||||||
struct ScholarshipInfo {
|
|
||||||
name: String,
|
|
||||||
provider_name: String,
|
|
||||||
relations: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
let client = create_dynamo_client().await;
|
|
||||||
|
|
||||||
let scholarships = dbg!(scan_table_items(&client, SCHOLARSHIPS_TABLE)
|
|
||||||
.await
|
|
||||||
.iter().map(|item| {
|
|
||||||
item.into_iter()
|
|
||||||
.map(|(k, v)| (k.clone(), ValueType::from(v)))
|
|
||||||
.collect()
|
|
||||||
}).collect::<Vec<HashMap<String, ValueType>>>()
|
|
||||||
);
|
|
||||||
let contacts = dbg!(
|
|
||||||
scan_table_items(&client, PROVIDER_CONTACT_TABLE).await
|
|
||||||
.iter().map(|item| {
|
|
||||||
item.into_iter()
|
|
||||||
.map(|(k, v)| (k.clone(), ValueType::from(v)))
|
|
||||||
.collect()
|
|
||||||
}).collect::<Vec<HashMap<String, ValueType>>>()
|
|
||||||
);
|
|
||||||
let db_relations = dbg!(
|
|
||||||
get_comparison_info().await.unwrap_or_default()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build a new structure containing some of the scholarship's information.
|
|
||||||
let resolved = scholarships.into_iter()
|
|
||||||
.map(|scholarship| {
|
|
||||||
// We just want the scholarship's name, the relations, and the provider's name.
|
|
||||||
// In here, we're going to resolve all the scholarship's information to provide this in
|
|
||||||
// a readable format.
|
|
||||||
let name = scholarship.get("name")
|
|
||||||
.unwrap_or(&ValueType::String(None))
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
// The list of relations - we're going to search the master list for each comparison's
|
|
||||||
// name. It's structured as a map of lists, or a ValueType::Map(Option<HashMap<String, ValueType::List(Option<Vec<ValueType::String()>>)>>)
|
|
||||||
let relations = scholarship.get("requirements")
|
|
||||||
.unwrap_or(&ValueType::Map(None))
|
|
||||||
.as_map().ok().flatten()
|
|
||||||
.unwrap_or_default() // We have our map of categories, now we need all the values inside each category.
|
|
||||||
.iter()
|
|
||||||
.map(|(_, list)| {
|
|
||||||
list.as_list().ok().flatten()
|
|
||||||
.unwrap_or_default()
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
.collect::<Vec<ValueType>>() // This is a Vec<ValueType::String>
|
|
||||||
.iter().map(|val| val.clone().as_string().ok().flatten().unwrap_or_default())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
// The provider ID, which we'll use to search for the provider's name.
|
|
||||||
let provider_id = scholarship.get("provider_id")
|
|
||||||
.unwrap_or(&ValueType::String(None))
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
// This is a mess, we'll clean it up later.
|
|
||||||
let parsed_relations = relations
|
|
||||||
.iter()
|
|
||||||
.filter_map(|relation| {
|
|
||||||
db_relations.iter().find(|search| {
|
|
||||||
search.id == *relation
|
|
||||||
})
|
|
||||||
.and_then(|search| Some(search.display_text.clone()))
|
|
||||||
})
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
|
|
||||||
ScholarshipInfo {
|
|
||||||
name,
|
|
||||||
provider_name: provider_id,
|
|
||||||
relations: parsed_relations,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<ScholarshipInfo>>();
|
|
||||||
|
|
||||||
// TODO Convert into CSV and send to client. For now I think I'm just going to serialize to json and print it.
|
|
||||||
leptos::logging::log!("Scholarships list: {:?}", resolved);
|
|
||||||
|
|
||||||
Ok(Vec::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn AdminUtilsPage() -> impl IntoView {
|
pub fn AdminUtilsPage() -> impl IntoView {
|
||||||
let create_comparisons = ServerAction::<CreateTestComparisons>::new();
|
let create_comparisons = ServerAction::<CreateTestComparisons>::new();
|
||||||
let create_dates = ServerAction::<CreateDates>::new();
|
let create_dates = ServerAction::<CreateDates>::new();
|
||||||
let create_export = ServerAction::<CreateScholarshipExport>::new();
|
let create_export = ServerAction::<GetScholarshipCsv>::new();
|
||||||
|
|
||||||
let on_click_comparisons = move |_| {
|
let on_click_comparisons = move |_| {
|
||||||
create_comparisons.dispatch(CreateTestComparisons{});
|
create_comparisons.dispatch(CreateTestComparisons {});
|
||||||
};
|
};
|
||||||
|
|
||||||
let on_click_dates = move |_| {
|
let on_click_dates = move |_| {
|
||||||
create_dates.dispatch(CreateDates{dates: get_important_dates()});
|
create_dates.dispatch(CreateDates {
|
||||||
|
dates: get_important_dates(),
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let on_click_export = move |_| {
|
let on_click_export = move |_| {
|
||||||
create_export.dispatch(CreateScholarshipExport{});
|
spawn_local(async move {
|
||||||
|
let file_bytes = get_scholarship_csv().await.unwrap_or_default();
|
||||||
|
|
||||||
|
let uint_arr = js_sys::Uint8Array::new_with_length(file_bytes.len() as u32);
|
||||||
|
uint_arr.copy_from(&file_bytes);
|
||||||
|
|
||||||
|
let blob_parts = js_sys::Array::new();
|
||||||
|
blob_parts.push(&uint_arr);
|
||||||
|
|
||||||
|
let blob = Blob::new_with_u8_array_sequence(&blob_parts).unwrap();
|
||||||
|
|
||||||
|
let blob_url = Url::create_object_url_with_blob(&blob).unwrap();
|
||||||
|
|
||||||
|
let window = window();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
let a: HtmlAnchorElement = document.create_element("a").unwrap().dyn_into().unwrap();
|
||||||
|
|
||||||
|
a.set_href(&blob_url);
|
||||||
|
a.set_download("scholarships.csv");
|
||||||
|
document.body().unwrap().append_child(&a).unwrap();
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
document.body().unwrap().remove_child(&a).unwrap();
|
||||||
|
Url::revoke_object_url(&blob_url).unwrap();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
|||||||
@@ -0,0 +1,237 @@
|
|||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
mod imports {
|
||||||
|
pub use super::super::{COMPARISONS_TABLE, PROVIDER_CONTACT_TABLE, SCHOLARSHIPS_TABLE};
|
||||||
|
pub use crate::common::ValueType;
|
||||||
|
pub use crate::utils::server::create_dynamo_client;
|
||||||
|
pub use aws_sdk_dynamodb::Client;
|
||||||
|
pub use aws_sdk_dynamodb::error::ProvideErrorMetadata;
|
||||||
|
pub use indexmap::IndexMap;
|
||||||
|
pub use std::collections::HashMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
use crate::common::ExpandableInfo;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn get_scholarship_csv() -> Result<Vec<u8>, ServerFnError> {
|
||||||
|
use imports::*;
|
||||||
|
use leptos::logging::debug_log;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct ResolvedScholarshipInfo {
|
||||||
|
contact_email: String,
|
||||||
|
first_name: String,
|
||||||
|
last_name: String,
|
||||||
|
scholarship_name: String,
|
||||||
|
sports_requirements: String,
|
||||||
|
gpa_requirements: String,
|
||||||
|
major_requirements: String,
|
||||||
|
additional_requirements: String,
|
||||||
|
community_requirements: String,
|
||||||
|
residency_requirements: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the scholarships and comparisons
|
||||||
|
let client = create_dynamo_client().await;
|
||||||
|
|
||||||
|
let scan_table = async |client: &Client, table_name| {
|
||||||
|
client
|
||||||
|
.scan()
|
||||||
|
.table_name(table_name)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map(|output| output.items)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|item| {
|
||||||
|
serde_dynamo::from_item(item).unwrap_or(ExpandableInfo::new("".to_string()))
|
||||||
|
// item.iter()
|
||||||
|
// .map(|(k, v)| (k.to_owned(), ValueType::from(v)))
|
||||||
|
// .collect::<HashMap<String, ValueType>>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<ExpandableInfo>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
let scan_table_hash_map = async |client: &Client, table_name| {
|
||||||
|
client
|
||||||
|
.scan()
|
||||||
|
.table_name(table_name)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map(|output| output.items)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|item| {
|
||||||
|
// serde_dynamo::from_item(item).unwrap_or(ExpandableInfo::new("".to_string()))
|
||||||
|
item.iter()
|
||||||
|
.map(|(k, v)| (k.to_owned(), ValueType::from(v)))
|
||||||
|
.collect::<HashMap<String, ValueType>>()
|
||||||
|
})
|
||||||
|
.collect::<Vec<HashMap<String, ValueType>>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
let master_scholarships = scan_table(&client, SCHOLARSHIPS_TABLE).await;
|
||||||
|
let master_contacts = scan_table_hash_map(&client, PROVIDER_CONTACT_TABLE).await;
|
||||||
|
let master_relations = scan_table_hash_map(&client, COMPARISONS_TABLE).await;
|
||||||
|
|
||||||
|
// We now want to map the relations to the scholarships, as well as the contacts.
|
||||||
|
// This should take the information from the current scholarship, then relate the IDs of the
|
||||||
|
// requirements in the scholarship to the IDs of the relations in the master list.
|
||||||
|
let resolved_scholarships = master_scholarships
|
||||||
|
.into_iter()
|
||||||
|
.map(|scholarship| {
|
||||||
|
// debug_log!("Scholarship: {:?}", scholarship);
|
||||||
|
// We want the requirements IDs and the provider ID.
|
||||||
|
let requirements_map = scholarship
|
||||||
|
.data
|
||||||
|
.get("requirements")
|
||||||
|
.unwrap_or(&ValueType::Map(None))
|
||||||
|
.as_map()
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let provider_id = scholarship
|
||||||
|
.data
|
||||||
|
.get("provider_id")
|
||||||
|
.map(|v| v.as_string().ok().flatten().unwrap_or_default())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Search for the provider ID in the master list.
|
||||||
|
let contact_info = master_contacts
|
||||||
|
.iter()
|
||||||
|
.find(|contact| {
|
||||||
|
let subject = contact
|
||||||
|
.get("subject")
|
||||||
|
.map(|v| v.as_string().ok().flatten().unwrap_or_default())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
debug_log!("Comparing {:?} and {:?}", subject, provider_id);
|
||||||
|
|
||||||
|
subject == provider_id
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Resolve the requirements - this should resolve as another category-separated map,
|
||||||
|
// but with requirement display text instead of IDs.
|
||||||
|
let resolved_requirements = requirements_map
|
||||||
|
.iter()
|
||||||
|
.map(|(category, selected_val)| {
|
||||||
|
let selected_list = selected_val.as_list().ok().flatten().unwrap_or_default();
|
||||||
|
|
||||||
|
let resolved_names = selected_list
|
||||||
|
.into_iter()
|
||||||
|
.map(|id_val| {
|
||||||
|
let id = id_val.as_string().ok().flatten().unwrap_or_default();
|
||||||
|
|
||||||
|
let resolved_relation = master_relations
|
||||||
|
.iter()
|
||||||
|
.find(|item| {
|
||||||
|
let comp_id = item
|
||||||
|
.get("id")
|
||||||
|
.map(|v| v.as_string().ok().flatten().unwrap_or_default())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
comp_id == id
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.map(|v| {
|
||||||
|
v.get("display_text").map(|text_opt| {
|
||||||
|
text_opt.as_string().ok().flatten().unwrap_or_default()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
resolved_relation
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
(category.clone(), resolved_names)
|
||||||
|
})
|
||||||
|
.collect::<IndexMap<String, Vec<String>>>();
|
||||||
|
debug_log!("Resolved requirements: {:?}", resolved_requirements);
|
||||||
|
|
||||||
|
let contact_email = contact_info
|
||||||
|
.get("contact_email")
|
||||||
|
.map(|v| v.as_string().ok().flatten().unwrap_or_default())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let first_name = contact_info
|
||||||
|
.get("first_name")
|
||||||
|
.map(|v| v.as_string().ok().flatten().unwrap_or_default())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let last_name = contact_info
|
||||||
|
.get("last_name")
|
||||||
|
.map(|v| v.as_string().ok().flatten().unwrap_or_default())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let scholarship_name = scholarship
|
||||||
|
.data
|
||||||
|
.get("name")
|
||||||
|
.map(|v| v.as_string().ok().flatten().unwrap_or_default())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
debug_log!(
|
||||||
|
"{:?}, {:?}, {:?}, {:?}",
|
||||||
|
scholarship_name,
|
||||||
|
contact_email,
|
||||||
|
first_name,
|
||||||
|
last_name
|
||||||
|
);
|
||||||
|
|
||||||
|
// We want to create some structure that's got the correct information. Specifically,
|
||||||
|
// we only need the provider contact info, the scholarship's name, and the requirements
|
||||||
|
// that they chose in plain text.
|
||||||
|
ResolvedScholarshipInfo {
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
contact_email,
|
||||||
|
scholarship_name,
|
||||||
|
sports_requirements: resolved_requirements
|
||||||
|
.get("sports_participation")
|
||||||
|
.map(|list| list.join("; "))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
gpa_requirements: resolved_requirements
|
||||||
|
.get("gpa")
|
||||||
|
.map(|list| list.join("; "))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
major_requirements: resolved_requirements
|
||||||
|
.get("major")
|
||||||
|
.map(|list| list.join("; "))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
additional_requirements: resolved_requirements
|
||||||
|
.get("misc")
|
||||||
|
.map(|list| list.join("; "))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
community_requirements: resolved_requirements
|
||||||
|
.get("community_service")
|
||||||
|
.map(|list| list.join("; "))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
residency_requirements: resolved_requirements
|
||||||
|
.get("residency")
|
||||||
|
.map(|list| list.join("; "))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<ResolvedScholarshipInfo>>();
|
||||||
|
|
||||||
|
// Now that we have a series of scholarships, we need to serialize them into CSV and send the
|
||||||
|
// file to the client.
|
||||||
|
let mut writer = csv::Writer::from_writer(Vec::new());
|
||||||
|
resolved_scholarships.iter().for_each(|scholarship| {
|
||||||
|
_ = writer.serialize(scholarship);
|
||||||
|
});
|
||||||
|
writer.flush()?;
|
||||||
|
|
||||||
|
let result = writer.into_inner()?;
|
||||||
|
debug_log!("{:?}", String::from_utf8(result.clone()));
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
mod comparisons;
|
|
||||||
mod scholarships;
|
|
||||||
mod dates;
|
|
||||||
mod admin;
|
mod admin;
|
||||||
|
mod comparisons;
|
||||||
|
mod dates;
|
||||||
|
pub mod exports;
|
||||||
mod providers;
|
mod providers;
|
||||||
|
mod scholarships;
|
||||||
pub mod students;
|
pub mod students;
|
||||||
|
|
||||||
pub use comparisons::*;
|
|
||||||
pub use scholarships::*;
|
|
||||||
pub use dates::*;
|
|
||||||
pub use admin::*;
|
pub use admin::*;
|
||||||
|
pub use comparisons::*;
|
||||||
|
pub use dates::*;
|
||||||
pub use providers::*;
|
pub use providers::*;
|
||||||
|
pub use scholarships::*;
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub static COMPARISONS_TABLE: &str = "leptos-comparisons";
|
pub static COMPARISONS_TABLE: &str = "leptos-comparisons";
|
||||||
|
|||||||
Reference in new issue
Block a user