feat: release build bundles templates in bin

This commit is contained in:
Per Stark
2025-03-27 08:32:21 +01:00
parent 0bc147cfc5
commit c8a97d9b52
70 changed files with 204 additions and 296 deletions

View File

@@ -1,10 +1,9 @@
use axum_session::SessionStore;
use axum_session_surreal::SessionSurrealPool;
use common::create_template_engine;
use common::storage::db::SurrealDbClient;
use common::utils::config::AppConfig;
use minijinja::{path_loader, Environment};
use minijinja_autoreload::AutoReloader;
use std::path::PathBuf;
use common::utils::template_engine::TemplateEngine;
use std::sync::Arc;
use surrealdb::engine::any::Any;
@@ -12,22 +11,13 @@ use surrealdb::engine::any::Any;
pub struct HtmlState {
pub db: Arc<SurrealDbClient>,
pub openai_client: Arc<async_openai::Client<async_openai::config::OpenAIConfig>>,
pub templates: Arc<AutoReloader>,
pub templates: Arc<TemplateEngine>,
pub session_store: Arc<SessionStore<SessionSurrealPool<Any>>>,
}
impl HtmlState {
pub async fn new(config: &AppConfig) -> Result<Self, Box<dyn std::error::Error>> {
let reloader = AutoReloader::new(move |notifier| {
let template_path = get_templates_dir();
let mut env = Environment::new();
env.set_loader(path_loader(&template_path));
notifier.set_fast_reload(true);
notifier.watch_path(&template_path, true);
minijinja_contrib::add_to_environment(&mut env);
Ok(env)
});
let template_engine = create_template_engine!("templates");
let surreal_db_client = Arc::new(
SurrealDbClient::new(
@@ -48,7 +38,7 @@ impl HtmlState {
let app_state = HtmlState {
db: surreal_db_client.clone(),
templates: Arc::new(reloader),
templates: Arc::new(template_engine),
openai_client: openai_client.clone(),
session_store,
};
@@ -56,23 +46,3 @@ impl HtmlState {
Ok(app_state)
}
}
pub fn get_workspace_root() -> PathBuf {
// Starts from CARGO_MANIFEST_DIR (e.g., /project/crates/html-router/)
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
// Navigate up to /path/to/project/crates
let crates_dir = manifest_dir
.parent()
.expect("Failed to find parent of manifest directory");
// Navigate up to workspace root
crates_dir
.parent()
.expect("Failed to find workspace root")
.to_path_buf()
}
pub fn get_templates_dir() -> PathBuf {
get_workspace_root().join("templates")
}

View File

@@ -1,3 +1,4 @@
use crate::html_state::HtmlState;
use axum::{
extract::State,
http::StatusCode,
@@ -6,19 +7,15 @@ use axum::{
};
use common::error::AppError;
use minijinja::{context, Value};
use minijinja_autoreload::AutoReloader;
use serde::Serialize;
use std::sync::Arc;
use tracing::error;
use crate::html_state::HtmlState;
// Enum for template types
#[derive(Clone)]
pub enum TemplateKind {
Full(String), // Full page template
Partial(String, String), // Template name, block name
Error(StatusCode), // Error template with status code
Redirect(String), // Redirect
Full(String),
Partial(String, String),
Error(StatusCode),
Redirect(String),
}
#[derive(Clone)]
@@ -53,14 +50,12 @@ impl TemplateResponse {
error => error,
description => description
};
Self {
template_kind: TemplateKind::Error(status),
context: ctx,
}
}
// Convenience methods for common errors
pub fn not_found() -> Self {
Self::error(
StatusCode::NOT_FOUND,
@@ -118,25 +113,33 @@ struct TemplateStateWrapper {
impl IntoResponse for TemplateStateWrapper {
fn into_response(self) -> Response {
let templates = self.state.templates;
let template_engine = &self.state.templates;
match &self.template_response.template_kind {
TemplateKind::Full(name) => {
render_template(name, self.template_response.context, templates)
match template_engine.render(name, &self.template_response.context) {
Ok(html) => Html(html).into_response(),
Err(e) => {
error!("Failed to render template: {:?}", e);
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
}
}
}
TemplateKind::Partial(name, block) => {
render_block(name, block, self.template_response.context, templates)
TemplateKind::Partial(template, block) => {
match template_engine.render_block(template, block, &self.template_response.context)
{
Ok(html) => Html(html).into_response(),
Err(e) => {
error!("Failed to render block: {:?}", e);
(StatusCode::INTERNAL_SERVER_ERROR, fallback_error()).into_response()
}
}
}
TemplateKind::Error(status) => {
let html = match try_render_template(
"errors/error.html",
self.template_response.context,
templates,
) {
Ok(html_string) => Html(html_string),
Err(_) => fallback_error(),
};
(*status, html).into_response()
match template_engine.render("errors/error.html", &self.template_response.context) {
Ok(html) => (*status, Html(html)).into_response(),
Err(_) => (*status, fallback_error()).into_response(),
}
}
TemplateKind::Redirect(path) => {
(StatusCode::OK, [(axum_htmx::HX_REDIRECT, path.clone())], "").into_response()
@@ -145,93 +148,11 @@ impl IntoResponse for TemplateStateWrapper {
}
}
// Helper functions for rendering with error handling
fn render_template(name: &str, context: Value, templates: Arc<AutoReloader>) -> Response {
match try_render_template(name, context, templates.clone()) {
Ok(html) => Html(html).into_response(),
Err(_) => fallback_error().into_response(),
}
}
fn render_block(name: &str, block: &str, context: Value, templates: Arc<AutoReloader>) -> Response {
match try_render_block(name, block, context, templates.clone()) {
Ok(html) => Html(html).into_response(),
Err(_) => fallback_error().into_response(),
}
}
fn try_render_template(
template_name: &str,
context: Value,
templates: Arc<AutoReloader>,
) -> Result<String, ()> {
let env = templates.acquire_env().map_err(|e| {
tracing::error!("Environment error: {:?}", e);
()
})?;
let tmpl = env.get_template(template_name).map_err(|e| {
tracing::error!("Template error: {:?}", e);
()
})?;
tmpl.render(context).map_err(|e| {
tracing::error!("Render error: {:?}", e);
()
})
}
fn try_render_block(
template_name: &str,
block: &str,
context: Value,
templates: Arc<AutoReloader>,
) -> Result<String, ()> {
let env = templates.acquire_env().map_err(|e| {
tracing::error!("Environment error: {:?}", e);
()
})?;
let tmpl = env.get_template(template_name).map_err(|e| {
tracing::error!("Template error: {:?}", e);
()
})?;
let mut state = tmpl.eval_to_state(context).map_err(|e| {
tracing::error!("Eval error: {:?}", e);
()
})?;
state.render_block(block).map_err(|e| {
tracing::error!("Block render error: {:?}", e);
()
})
}
fn fallback_error() -> Html<String> {
Html(
r#"
<html>
<body>
<div class="container mx-auto p-4">
<h1 class="text-4xl text-error">Error</h1>
<p class="mt-4">Sorry, something went wrong displaying this page.</p>
</div>
</body>
</html>
"#
.to_string(),
)
}
pub async fn with_template_response(
State(state): State<HtmlState>,
response: Response,
) -> Response {
// Clone the TemplateResponse from extensions
let template_response = response.extensions().get::<TemplateResponse>().cloned();
if let Some(template_response) = template_response {
if let Some(template_response) = response.extensions().get::<TemplateResponse>().cloned() {
TemplateStateWrapper {
state,
template_response,
@@ -242,40 +163,60 @@ pub async fn with_template_response(
}
}
// Define HtmlError
#[derive(Debug)]
pub enum HtmlError {
AppError(AppError),
TemplateError(String),
}
// Conversion from AppError to HtmlError
impl From<AppError> for HtmlError {
fn from(err: AppError) -> Self {
HtmlError::AppError(err)
}
}
// Conversion for database error to HtmlError
impl From<surrealdb::Error> for HtmlError {
fn from(err: surrealdb::Error) -> Self {
HtmlError::AppError(AppError::from(err))
}
}
impl From<minijinja::Error> for HtmlError {
fn from(err: minijinja::Error) -> Self {
HtmlError::TemplateError(err.to_string())
}
}
impl IntoResponse for HtmlError {
fn into_response(self) -> Response {
match self {
HtmlError::AppError(err) => {
let template_response = match err {
AppError::NotFound(_) => TemplateResponse::not_found(),
AppError::Auth(_) => TemplateResponse::unauthorized(),
AppError::Validation(msg) => TemplateResponse::bad_request(&msg),
_ => {
tracing::error!("Internal error: {:?}", err);
TemplateResponse::server_error()
}
};
template_response.into_response()
HtmlError::AppError(err) => match err {
AppError::NotFound(_) => TemplateResponse::not_found().into_response(),
AppError::Auth(_) => TemplateResponse::unauthorized().into_response(),
AppError::Validation(msg) => TemplateResponse::bad_request(&msg).into_response(),
_ => {
error!("Internal error: {:?}", err);
TemplateResponse::server_error().into_response()
}
},
HtmlError::TemplateError(err) => {
error!("Template error: {}", err);
TemplateResponse::server_error().into_response()
}
}
}
}
fn fallback_error() -> String {
r#"
<html>
<body>
<div class="container mx-auto p-4">
<h1 class="text-4xl text-error">Error</h1>
<p class="mt-4">Sorry, something went wrong displaying this page.</p>
</div>
</body>
</html>
"#
.to_string()
}

View File

@@ -22,23 +22,24 @@ use futures::{
Stream, StreamExt, TryStreamExt,
};
use json_stream_parser::JsonStreamParser;
use minijinja::Value;
use serde::{Deserialize, Serialize};
use serde_json::from_str;
use surrealdb::{engine::any::Any, Surreal};
use tokio::sync::{mpsc::channel, Mutex};
use tracing::{error, debug};
use tracing::{debug, error};
use common::storage::{
db::SurrealDbClient,
types::{
conversation::Conversation,
message::{Message, MessageRole},
user::User,
system_settings::SystemSettings,
user::User,
},
};
use crate::{html_state::HtmlState, routes::render_template};
use crate::html_state::HtmlState;
// Error handling function
fn create_error_stream(
@@ -272,17 +273,13 @@ pub async fn get_response_stream(
}
// Render template with references
match render_template(
match state.templates.render(
"chat/reference_list.html",
ReferenceData { message },
state.templates.clone(),
&Value::from_serialize(ReferenceData { message }),
) {
Ok(html) => {
// Extract the String from Html<String>
let html_string = html.0;
// Return the rendered HTML
Ok(Event::default().event("references").data(html_string))
Ok(Event::default().event("references").data(html))
}
Err(_) => {
// Handle template rendering error

View File

@@ -1,10 +1,3 @@
use std::sync::Arc;
use axum::response::Html;
use minijinja_autoreload::AutoReloader;
use crate::middlewares::response_middleware::HtmlError;
pub mod account;
pub mod admin;
pub mod auth;
@@ -14,20 +7,3 @@ pub mod index;
pub mod ingestion;
pub mod knowledge;
pub mod search;
// Helper function for render_template
pub fn render_template<T>(
template_name: &str,
context: T,
templates: Arc<AutoReloader>,
) -> Result<Html<String>, HtmlError>
where
T: serde::Serialize,
{
let env = templates.acquire_env().unwrap();
let tmpl = env.get_template(template_name).unwrap();
let context = minijinja::Value::from_serialize(&context);
let output = tmpl.render(context).unwrap();
Ok(Html(output))
}