feat: add pdf gen capability
This commit is contained in:
1 parent
3cc67cb9e6
commit
8282ba4302
5 files changed
+370
-16
No files matched your search
@@ -2,6 +2,7 @@ use leptos::prelude::*;
|
||||
use leptos::server_fn::codec::Json;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::process::Stdio;
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
mod imports {
|
||||
@@ -14,8 +15,12 @@ mod imports {
|
||||
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::process::Command;
|
||||
}
|
||||
|
||||
static PDF_TEMPLATE: &str = include_str!("../../../pdf_template.typ");
|
||||
|
||||
#[server(input = Json)]
|
||||
pub async fn put_student_data(
|
||||
subject: String,
|
||||
@@ -512,3 +517,152 @@ pub async fn provider_get_all_input_files(
|
||||
|
||||
get_all_input_files(form_name, input_name).await
|
||||
}
|
||||
|
||||
#[server]
|
||||
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.
|
||||
|
||||
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")
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let last_name = student_info.get("last_name")
|
||||
.map(|v| v.to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let typst_string = student_info_str
|
||||
.replace("{", "(")
|
||||
.replace("}", ")")
|
||||
.replace("[", "(")
|
||||
.replace("]", ")")
|
||||
// Ensures trailing commas for lists of dictionaries
|
||||
.replace("))", "),)");
|
||||
|
||||
debug_log!("New string: {}", typst_string);
|
||||
|
||||
let pdf_string = format!("#let student = {typst_string}\n{PDF_TEMPLATE}");
|
||||
debug_log!("PDF string: {}", pdf_string);
|
||||
let mut child = Command::new("typst")
|
||||
.arg("compile")
|
||||
.arg("-")
|
||||
.arg("-")
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
{
|
||||
let stdin = child.stdin.as_mut().unwrap();
|
||||
stdin.write_all(pdf_string.as_bytes())?;
|
||||
stdin.flush()?;
|
||||
}
|
||||
|
||||
let output = child.wait_with_output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
let msg = String::from_utf8_lossy(&output.stderr).to_string();
|
||||
error!("Failed to generate PDF: {}", msg);
|
||||
Err(ServerFnError::new(format!("Failed to generate PDF: {msg}")))
|
||||
} else {
|
||||
let bytes = output.stdout;
|
||||
let file_name = format!("{first_name}{last_name}_Application.pdf");
|
||||
Ok((file_name, bytes))
|
||||
}
|
||||
}
|
||||
|
||||
#[server]
|
||||
pub async fn get_student_info_json(
|
||||
student_id: String,
|
||||
) -> Result<String, ServerFnError> {
|
||||
use imports::*;
|
||||
|
||||
let client = create_dynamo_client().await;
|
||||
|
||||
let response = client
|
||||
.query()
|
||||
.table_name(MAIN_TABLE_NAME)
|
||||
.expression_attribute_values(":hk", AttributeValue::S(format!("STUDENT#{student_id}")))
|
||||
.key_condition_expression("HK = :hk")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let msg = e.message().unwrap_or("Unknown error occurred");
|
||||
error!("{}", msg);
|
||||
ServerFnError::new(msg)
|
||||
})?;
|
||||
|
||||
let work_exp = client
|
||||
.query()
|
||||
.table_name(MAIN_TABLE_NAME)
|
||||
.expression_attribute_values(":hk", AttributeValue::S(format!("STUDENT#{student_id}")))
|
||||
.expression_attribute_values(":sk", AttributeValue::S("WORKEXP".to_string()))
|
||||
.key_condition_expression("HK = :hk AND SK = :sk")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let msg = e.message().unwrap_or("Unknown error occurred");
|
||||
error!("{}", msg);
|
||||
ServerFnError::new(msg)
|
||||
})
|
||||
.map(|output| {
|
||||
let items = output.items.unwrap_or_default();
|
||||
debug_log!("Number of items: {}", items.len());
|
||||
items.into_iter()
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.get("extracurricular")
|
||||
.cloned()
|
||||
.unwrap_or(AttributeValue::S("".to_string()))
|
||||
})?;
|
||||
|
||||
let extracurricular = client
|
||||
.query()
|
||||
.table_name(MAIN_TABLE_NAME)
|
||||
.expression_attribute_values(":hk", AttributeValue::S(format!("STUDENT#{student_id}")))
|
||||
.expression_attribute_values(":sk", AttributeValue::S("DEMOGRAPHICS".to_string()))
|
||||
.key_condition_expression("HK = :hk AND SK = :sk")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
let msg = e.message().unwrap_or("Unknown error occurred");
|
||||
error!("{}", msg);
|
||||
ServerFnError::new(msg)
|
||||
})
|
||||
.map(|output| {
|
||||
let items = output.items.unwrap_or_default();
|
||||
debug_log!("Number of items: {}", items.len());
|
||||
items.into_iter()
|
||||
.next()
|
||||
.unwrap_or_default()
|
||||
.get("extracurricular")
|
||||
.cloned()
|
||||
.unwrap_or(AttributeValue::S("".to_string()))
|
||||
})?;
|
||||
|
||||
let Some(items) = response.items else {
|
||||
return Err(ServerFnError::new("Failed to find student information."));
|
||||
};
|
||||
|
||||
// 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
|
||||
});
|
||||
|
||||
data.insert("work_experience".to_string(), work_exp);
|
||||
data.insert("extracurricular".to_string(), extracurricular);
|
||||
|
||||
let json_value: serde_json::Value = serde_dynamo::from_item(data)?;
|
||||
let json_string = serde_json::to_string(&json_value)?;
|
||||
println!("{}", json_string);
|
||||
|
||||
Ok(json_string)
|
||||
}
|
||||
Reference in new issue
Block a user