wip
This commit is contained in:
@@ -32,7 +32,6 @@ impl Fairing for Chromium {
|
||||
|
||||
async fn on_ignite(&self, rocket: Rocket<Build>) -> fairing::Result {
|
||||
let new_rocket = rocket.manage(ChromiumCoordinator::new().await);
|
||||
let coordinator = new_rocket.state::<ChromiumCoordinator>().unwrap();
|
||||
Ok(new_rocket)
|
||||
}
|
||||
}
|
||||
|
||||
14
src/lib.rs
14
src/lib.rs
@@ -1,15 +1,27 @@
|
||||
use chromium::rocket::Chromium;
|
||||
use nanobyte_opentelemetry::rocket::TracingFairing;
|
||||
use rocket::{Rocket, Build, routes};
|
||||
use rocket::{Rocket, Build, routes, fairing::AdHoc};
|
||||
use serde::Deserialize;
|
||||
pub mod routes;
|
||||
mod chromium;
|
||||
mod tools;
|
||||
mod services;
|
||||
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct CVBackendConfig {
|
||||
cv_backend_path: String,
|
||||
}
|
||||
|
||||
pub fn rocket_builder() -> Rocket<Build> {
|
||||
rocket::build()
|
||||
.attach(TracingFairing::ignite())
|
||||
.attach(Chromium::ignite())
|
||||
.attach(
|
||||
AdHoc::config::<CVBackendConfig>()
|
||||
)
|
||||
.mount("/", routes![
|
||||
routes::pdf::render_pdf_cv,
|
||||
routes::pdf::render_html_cv
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
use headless_chrome::Browser;
|
||||
use headless_chrome::types::PrintToPdfOptions;
|
||||
use nanobyte_opentelemetry::rocket::TracingSpan;
|
||||
use rocket::{get, response::stream::ByteStream, fairing::Fairing, fs::NamedFile, futures::TryFutureExt};
|
||||
use nanobyte_opentelemetry::rocket::{TracingSpan, RequestId, OtelReqwestClient};
|
||||
use ovlach_data::cv::cv::CV;
|
||||
use reqwest::Client;
|
||||
use rocket::fs::NamedFile;
|
||||
use ::rocket::{State, http::Status};
|
||||
use ::rocket::get;
|
||||
use tempfile::NamedTempFile;
|
||||
use tera::Context;
|
||||
use tracing::{info_span, error, debug};
|
||||
use crate::{chromium::rocket::BrowserHolder, tools::{tera::NanoTera, pdf::PdfStream}};
|
||||
use urlencoding::encode;
|
||||
use crate::{chromium::rocket::BrowserHolder, tools::{tera::NanoTera, pdf::PdfStream, rocket::RequestLanguage}, services::cv::fetch_cv_data_from_backend, CVBackendConfig};
|
||||
|
||||
// TODO: request-id
|
||||
fn generate_pdf(browser: Browser, file: &NamedTempFile) -> Vec<u8> {
|
||||
@@ -38,29 +41,59 @@ fn generate_pdf(browser: Browser, file: &NamedTempFile) -> Vec<u8> {
|
||||
}
|
||||
|
||||
#[tracing::instrument]
|
||||
fn render_template(template_name: &str, file: &NamedTempFile, tera: NanoTera) {
|
||||
fn render_template(template_name: &str, file: &NamedTempFile, tera: NanoTera, cv: CV, language: String) {
|
||||
// TODO: handle errors
|
||||
tera.0.render_to("two_column.html.tera", &Context::new(), file);
|
||||
let mut tera_context = Context::new();
|
||||
tera_context.insert("cv", &cv);
|
||||
tera_context.insert("lang", language.as_str());
|
||||
|
||||
match tera.0.render_to("two_column.html.tera", &tera_context, file) {
|
||||
Ok(_) => {
|
||||
debug!("Rendered template to {}", file.path().to_str().unwrap());
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error rendering template {}: {}", &template_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/cv/<username>/output.pdf")]
|
||||
pub async fn render_pdf_cv(username: &str, browser: BrowserHolder, tera: NanoTera, tracing: TracingSpan) -> PdfStream {
|
||||
#[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> {
|
||||
let entered_span = tracing.0.enter();
|
||||
let file = tempfile::Builder::new().suffix(".html").tempfile().unwrap();
|
||||
render_template("two_column", &file, tera);
|
||||
let span = info_span!("render_pdf", username = username);
|
||||
let pdf = span.in_scope(||{
|
||||
generate_pdf(browser.browser, &file)
|
||||
});
|
||||
drop(entered_span);
|
||||
PdfStream::new(pdf)
|
||||
match fetch_cv_data_from_backend(cv_config.cv_backend_path.clone(), request_client.0).await {
|
||||
Ok(cv_data) => {
|
||||
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(||{
|
||||
generate_pdf(browser.browser, &file)
|
||||
});
|
||||
drop(entered_span);
|
||||
Ok(PdfStream::new(pdf))
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error fetching cv data: {:?}", e);
|
||||
Err(Status::InternalServerError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Route only for debuging
|
||||
#[get("/cv/<username>/output.html")]
|
||||
pub async fn render_html_cv(username: &str, tera: NanoTera, tracing: TracingSpan) -> NamedFile {
|
||||
let entered_span = tracing.0.enter();
|
||||
let file = tempfile::Builder::new().suffix(".html").tempfile().unwrap();
|
||||
render_template("two_column", &file, tera);
|
||||
NamedFile::open(file.path()).await.unwrap()
|
||||
#[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> {
|
||||
let _ = tracing.0.enter();
|
||||
match fetch_cv_data_from_backend(cv_config.cv_backend_path.clone(), request_client.0).await {
|
||||
Ok(cv_data) => {
|
||||
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())
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Error fetching cv data: {:?}", e);
|
||||
Err(Status::InternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
23
src/services/cv.rs
Normal file
23
src/services/cv.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use ovlach_data::cv::cv::CV;
|
||||
use reqwest::Client;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FetchError {
|
||||
ReqwestError(reqwest::Error)
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for FetchError {
|
||||
fn from(e: reqwest::Error) -> Self {
|
||||
FetchError::ReqwestError(e)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn fetch_cv_data_from_backend(backend_host: String, client: Client) -> Result<CV, FetchError> {
|
||||
let resp = client
|
||||
.get(format!("{}/{}", backend_host, "/api/cv/ovlach")).send()
|
||||
.await?
|
||||
.json::<CV>()
|
||||
.await?;
|
||||
Ok(resp)
|
||||
}
|
||||
1
src/services/mod.rs
Normal file
1
src/services/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod cv;
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod tera;
|
||||
pub mod pdf;
|
||||
pub mod pdf;
|
||||
pub(crate) mod rocket;
|
||||
26
src/tools/rocket.rs
Normal file
26
src/tools/rocket.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use phf::phf_map;
|
||||
use rocket::request::FromParam;
|
||||
|
||||
pub struct RequestLanguage {
|
||||
pub language: String,
|
||||
}
|
||||
|
||||
static LANG_TO_CODES: phf::Map<&'static str, &'static str> = phf_map! {
|
||||
"cs" => "cs-CZ",
|
||||
"en" => "en-US",
|
||||
};
|
||||
|
||||
impl<'r> FromParam<'r> for RequestLanguage {
|
||||
type Error = &'r str;
|
||||
|
||||
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
||||
match LANG_TO_CODES.get(param) {
|
||||
Some(val) => Ok(RequestLanguage {
|
||||
language: val.to_string(),
|
||||
}),
|
||||
None => Ok(RequestLanguage {
|
||||
language: LANG_TO_CODES["en"].to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
use std::{collections::HashMap};
|
||||
|
||||
use nanobyte_tera::{l18n::translate_filter, date::{calculate_age, get_year}, string::insert_space_every, gravatar::gravatar_link};
|
||||
use ovlach_tera::entity::lang_entity;
|
||||
use rocket::{request::{FromRequest, Outcome}, Request};
|
||||
use tera::Tera;
|
||||
use tera::{Tera, Value, Error};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NanoTera(pub Tera);
|
||||
@@ -11,6 +15,33 @@ impl<'r> FromRequest<'r> for NanoTera {
|
||||
type Error = ();
|
||||
|
||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, ()> {
|
||||
rocket::outcome::Outcome::Success(NanoTera(Tera::new("templates/*").unwrap()))
|
||||
let mut tera = Tera::new("templates/*").unwrap();
|
||||
tera.register_filter("translate", translate_filter);
|
||||
tera.register_filter("calculate_age", calculate_age);
|
||||
tera.register_filter("insert_space_every", insert_space_every);
|
||||
tera.register_filter("gravatar_link", gravatar_link);
|
||||
// filters specific to API
|
||||
tera.register_filter("lang_entity", lang_entity);
|
||||
tera.register_filter("format_date", get_year); // deprecated
|
||||
tera.register_filter("get_year", get_year);
|
||||
tera.register_filter("strip_proto", strip_proto);
|
||||
rocket::outcome::Outcome::Success(NanoTera(tera))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Strip protocol from URL (value)
|
||||
pub fn strip_proto(
|
||||
tera_value: &Value,
|
||||
_: &HashMap<String, Value>
|
||||
) -> Result<Value, Error> {
|
||||
let value = tera_value.as_str().unwrap();
|
||||
if value.starts_with("http://") {
|
||||
Ok(Value::String(value.strip_prefix("http://").unwrap().to_string()))
|
||||
} else if value.starts_with("https://") {
|
||||
Ok(Value::String(value.strip_prefix("https://").unwrap().to_string()))
|
||||
} else {
|
||||
Ok(Value::String(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user