tmp commit

This commit is contained in:
2023-12-03 21:43:12 +01:00
parent 7ea9bbbb5f
commit 8a035affe3
11 changed files with 273 additions and 75 deletions

View File

@@ -15,6 +15,13 @@ pub struct CVBackendConfig {
cv_backend_path: String,
}
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct DefaultPerson {
default_person_name: String,
}
pub fn rocket_builder() -> Rocket<Build> {
rocket::build()
.attach(TracingFairing::ignite())
@@ -22,8 +29,11 @@ pub fn rocket_builder() -> Rocket<Build> {
.attach(
AdHoc::config::<CVBackendConfig>()
)
.attach(
AdHoc::config::<DefaultPerson>()
)
.mount("/", routes![
routes::pdf::render_pdf_cv,
routes::pdf::render_html_cv
])
}
}

View File

@@ -11,8 +11,9 @@ async fn main() {
install_panic_handler();
let _opentelemetry = nanobyte_opentelemetry::init_telemetry(
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION"),
env!("CARGO_PKG_VERSION"),
&std::env::var("OTLP_ENDPOINT").unwrap_or("".to_string()),
Some(default_filter_layer(LogLevel::DebugWithoutRs))
);
let _ = rocket_builder().launch().await;
}
}

View File

@@ -1,7 +1,8 @@
use headless_chrome::Browser;
use headless_chrome::types::PrintToPdfOptions;
use nanobyte_opentelemetry::rocket::{TracingSpan, RequestId, OtelReqwestClient};
use ovlach_data::cv::cv::CV;
use nanobyte_tera::l18n::LanguageDescription;
use ovlach_data::cv::data::CV;
use reqwest::Client;
use rocket::fs::NamedFile;
use ::rocket::{State, http::Status};
@@ -9,6 +10,7 @@ use ::rocket::get;
use tempfile::NamedTempFile;
use tera::Context;
use tracing::{info_span, error, debug};
use crate::DefaultPerson;
use crate::{chromium::rocket::BrowserHolder, tools::{tera::NanoTera, pdf::PdfStream, rocket::RequestLanguage}, services::cv::fetch_cv_data_from_backend, CVBackendConfig};
// TODO: request-id
@@ -31,7 +33,7 @@ fn generate_pdf(browser: Browser, file: &NamedTempFile) -> Vec<u8> {
..PrintToPdfOptions::default()
};
//thread::sleep(Duration::from_secs(10));
//thread::sleep(Duration::from_secs(10));
let bytes = info_span!("print_pdf").in_scope(|| {
tab.print_to_pdf(Some(options)).unwrap()
@@ -45,8 +47,8 @@ fn render_template(template_name: &str, file: &NamedTempFile, tera: NanoTera, cv
// TODO: handle errors
let mut tera_context = Context::new();
tera_context.insert("cv", &cv);
tera_context.insert("lang", language.as_str());
tera_context.insert("lang", &LanguageDescription::new(&language, "ovlach_pdf"));
match tera.0.render_to("two_column.html.tera", &tera_context, file) {
Ok(_) => {
debug!("Rendered template to {}", file.path().to_str().unwrap());
@@ -59,11 +61,12 @@ fn render_template(template_name: &str, file: &NamedTempFile, tera: NanoTera, cv
#[get("/cv/<username>/<language>/output.pdf")]
pub async fn render_pdf_cv(username: &str, tera: NanoTera, tracing: TracingSpan, request_client: OtelReqwestClient,
cv_config: &State<CVBackendConfig>, language: RequestLanguage, browser: BrowserHolder) -> Result<PdfStream, Status> {
cv_config: &State<CVBackendConfig>, language: RequestLanguage, browser: BrowserHolder,
default_person: &State<DefaultPerson>) -> Result<PdfStream, Status> {
let entered_span = tracing.0.enter();
match fetch_cv_data_from_backend(cv_config.cv_backend_path.clone(), request_client.0).await {
match fetch_cv_data_from_backend(&cv_config.cv_backend_path, &default_person.inner().default_person_name, &request_client.0).await {
Ok(cv_data) => {
let file = tempfile::Builder::new().suffix(".html").tempfile().unwrap();
let file = tempfile::Builder::new().suffix(".html").tempfile().unwrap();
render_template("two_column", &file, tera, cv_data, language.language);
let span = info_span!("render_pdf", username = username);
let pdf = span.in_scope(||{
@@ -82,11 +85,12 @@ pub async fn render_pdf_cv(username: &str, tera: NanoTera, tracing: TracingSpan,
/// Route only for debuging
#[get("/cv/<username>/<language>/output.html")]
pub async fn render_html_cv(username: &str, tera: NanoTera, tracing: TracingSpan, request_client: OtelReqwestClient,
cv_config: &State<CVBackendConfig>, language: RequestLanguage) -> Result<NamedFile, Status> {
cv_config: &State<CVBackendConfig>, language: RequestLanguage,
default_person: &State<DefaultPerson>) -> Result<NamedFile, Status> {
let _ = tracing.0.enter();
match fetch_cv_data_from_backend(cv_config.cv_backend_path.clone(), request_client.0).await {
match fetch_cv_data_from_backend(&cv_config.cv_backend_path, &default_person.inner().default_person_name, &request_client.0).await {
Ok(cv_data) => {
let file = tempfile::Builder::new().suffix(".html").tempfile().unwrap();
let file = tempfile::Builder::new().suffix(".html").tempfile().unwrap();
render_template("two_column", &file, tera, cv_data, language.language);
Ok(NamedFile::open(file.path()).await.unwrap())
},
@@ -95,5 +99,5 @@ pub async fn render_html_cv(username: &str, tera: NanoTera, tracing: TracingSpan
Err(Status::InternalServerError)
}
}
}
}

View File

@@ -1,5 +1,6 @@
use ovlach_data::cv::cv::CV;
use ovlach_data::cv::data::CV;
use reqwest::Client;
use tracing::{debug, instrument};
#[derive(Debug)]
@@ -13,11 +14,14 @@ impl From<reqwest::Error> for FetchError {
}
}
pub async fn fetch_cv_data_from_backend(backend_host: String, client: Client) -> Result<CV, FetchError> {
#[instrument]
pub async fn fetch_cv_data_from_backend(backend_host: &String, person_name: &String, client: &Client) -> Result<CV, FetchError> {
let url = format!("{}/{}/{}", backend_host, "api/v1/cv", person_name);
debug!("Fetching CV data from backend: {}", url);
let resp = client
.get(format!("{}/{}", backend_host, "/api/cv/ovlach")).send()
.get(url).send()
.await?
.json::<CV>()
.await?;
Ok(resp)
}
}

View File

@@ -25,6 +25,7 @@ impl<'r> FromRequest<'r> for NanoTera {
tera.register_filter("format_date", get_year); // deprecated
tera.register_filter("get_year", get_year);
tera.register_filter("strip_proto", strip_proto);
tera.register_filter("advanced_filter", advanced_filter);
rocket::outcome::Outcome::Success(NanoTera(tera))
}
}
@@ -45,3 +46,113 @@ pub fn strip_proto(
}
}
use tera::{try_get_value, to_value};
// TODO: move to other library
/// If the `value` is not passed, optionally discard all elements where the attribute is null. (include_null -> all, none, only)
pub fn advanced_filter(value: &Value, args: &HashMap<String, Value>) -> Result<tera::Value, tera::Error> {
let mut arr = try_get_value!("filter", "value", Vec<Value>, value);
if arr.is_empty() {
return Ok(arr.into());
}
let key = match args.get("attribute") {
Some(val) => try_get_value!("filter", "attribute", String, val),
None => return Err(Error::msg("The `filter` filter has to have an `attribute` argument")),
};
let null = match args.get("include_null") {
Some(val) => try_get_value!("filter", "attribute", String, val),
None => "none".to_string(),
};
let against_value = match args.get("value") {
Some(val) => Some(try_get_value!("filter", "value", String, val)),
None => None
};
if null == "only" && against_value.is_some() {
return Err(Error::msg("The `filter` filter cannot have both `include_null=only` and `value`"))
}
arr = arr
.into_iter()
.filter(|v| {
let tested_value = v.get(key.clone()).unwrap_or(&Value::Null);
if tested_value.is_null() {
return match null.as_str() {
"all" => true,
"none" => false,
"only" => true,
_ => false,
}
} else if null != "only" {
let val = tested_value.as_str();
match val {
Some(match_v) => {
match against_value.clone() {
Some(against_v) => match_v == against_v,
None => true,
}
}
None => true,
}
} else {
false
}
})
.collect::<Vec<_>>();
Ok(to_value(arr).unwrap())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use std::collections::HashMap;
fn call_advanced_filter(data: &Vec<TestClass>, include_null: &str, value: Option<&str>) -> Result<Value, Error> {
let mut args = HashMap::new();
args.insert("attribute".to_string(), json!("against_value"));
args.insert("include_null".to_string(), json!(include_null));
if let Some(val) = value {
args.insert("value".to_string(), json!(val));
}
advanced_filter(&json!(data), &args)
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
struct TestClass {
against_value: Option<String>,
}
impl TestClass {
pub fn new(against_value: Option<String>) -> Self {
TestClass { against_value }
}
}
fn generate_test_class(first_data: Option<String>, second_data: Option<String>) -> Vec<TestClass> {
vec![TestClass::new(first_data), TestClass::new(second_data)]
}
#[test]
fn test_advanced_filter() {
let data = vec![TestClass::new(Some("foo".to_string())), TestClass::new(None)];
let result = call_advanced_filter(&data, "all", None).unwrap();
assert_eq!(result, json!(generate_test_class(Some("foo".to_string()), None)));
let result = call_advanced_filter(&data, "none", None).unwrap();
assert_eq!(result, json!(vec![TestClass::new(Some("foo".to_string()))]));
let result = call_advanced_filter(&data, "only", None).unwrap();
assert_eq!(result, json!(vec![TestClass::new(None)]));
// Test filtering strings
let result = call_advanced_filter(&data, "all", Some("foo")).unwrap();
assert_eq!(result, json!(vec![TestClass::new(Some("foo".to_string())), TestClass::new(None)]));
let result = call_advanced_filter(&data, "none", Some("bar")).unwrap();
let vec: Vec<TestClass> = vec![];
assert_eq!(result, json!(vec));
let result = call_advanced_filter(&data, "only", Some("zz")).is_err();
assert!(result);
}
}