mirror of
https://github.com/perstarkse/minne.git
synced 2026-02-24 17:14:50 +01:00
improved htmlerror ergonomics
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -30,7 +30,7 @@ use zettle_db::{
|
||||
documentation::index::show_documentation_index,
|
||||
gdpr::{accept_gdpr, deny_gdpr},
|
||||
index::index_handler,
|
||||
ingress::{process_ingress_form, show_ingress_form},
|
||||
ingress_form::{process_ingress_form, show_ingress_form},
|
||||
ingress_tasks::{delete_task, show_queue_tasks},
|
||||
privacy_policy::show_privacy_policy,
|
||||
search_result::search_result_handler,
|
||||
@@ -175,7 +175,7 @@ fn html_routes(
|
||||
.route("/signout", get(sign_out_user))
|
||||
.route("/signin", get(show_signin_form).post(authenticate_user))
|
||||
.route(
|
||||
"/ingress",
|
||||
"/ingress-form",
|
||||
get(show_ingress_form).post(process_ingress_form),
|
||||
)
|
||||
.route("/queue", get(show_queue_tasks))
|
||||
|
||||
21
src/error.rs
21
src/error.rs
@@ -109,6 +109,26 @@ impl IntoResponse for ApiError {
|
||||
(status, Json(body)).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub type TemplateResult<T> = Result<T, HtmlError>;
|
||||
|
||||
// Helper trait for converting to HtmlError with templates
|
||||
pub trait IntoHtmlError {
|
||||
fn with_template(self, templates: Arc<AutoReloader>) -> HtmlError;
|
||||
}
|
||||
// // Implement for AppError
|
||||
impl IntoHtmlError for AppError {
|
||||
fn with_template(self, templates: Arc<AutoReloader>) -> HtmlError {
|
||||
HtmlError::new(self, templates)
|
||||
}
|
||||
}
|
||||
// // Implement for minijinja::Error directly
|
||||
impl IntoHtmlError for minijinja::Error {
|
||||
fn with_template(self, templates: Arc<AutoReloader>) -> HtmlError {
|
||||
HtmlError::from_template_error(self, templates)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ErrorContext {
|
||||
#[allow(dead_code)]
|
||||
@@ -129,7 +149,6 @@ pub enum HtmlError {
|
||||
Template(String, Arc<AutoReloader>),
|
||||
}
|
||||
|
||||
// Implement From<ApiError> for HtmlError
|
||||
impl HtmlError {
|
||||
pub fn new(error: AppError, templates: Arc<AutoReloader>) -> Self {
|
||||
match error {
|
||||
|
||||
@@ -35,8 +35,7 @@ pub async fn show_account_page(
|
||||
AccountData::template_name(),
|
||||
AccountData { user },
|
||||
state.templates.clone(),
|
||||
)
|
||||
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?;
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
@@ -70,8 +69,7 @@ pub async fn set_api_key(
|
||||
"api_key_section",
|
||||
AccountData { user: updated_user },
|
||||
state.templates.clone(),
|
||||
)
|
||||
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?;
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ pub async fn show_documentation_index(
|
||||
user: auth.current_user,
|
||||
},
|
||||
state.templates.clone(),
|
||||
)
|
||||
.map_err(|e| HtmlError::from_template_error(e, state.templates.clone()))?;
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use surrealdb::{engine::any::Any, Surreal};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
error::{AppError, HtmlError},
|
||||
error::HtmlError,
|
||||
page_data,
|
||||
server::{routes::html::render_template, AppState},
|
||||
storage::types::user::User,
|
||||
@@ -57,8 +57,7 @@ pub async fn index_handler(
|
||||
user: auth.current_user,
|
||||
},
|
||||
state.templates.clone(),
|
||||
)
|
||||
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?;
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
@@ -6,25 +6,20 @@ use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use axum_typed_multipart::{FieldData, TryFromMultipart, TypedMultipart};
|
||||
use futures::{future::try_join_all, TryFutureExt};
|
||||
use serde::Serialize;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
use tempfile::NamedTempFile;
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
error::{AppError, HtmlError},
|
||||
error::{AppError, HtmlError, IntoHtmlError},
|
||||
ingress::types::ingress_input::{create_ingress_objects, IngressInput},
|
||||
page_data,
|
||||
server::AppState,
|
||||
storage::types::{file_info::FileInfo, user::User},
|
||||
};
|
||||
|
||||
use super::render_template;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PageData {
|
||||
// name: String,
|
||||
}
|
||||
|
||||
pub async fn show_ingress_form(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
@@ -33,8 +28,7 @@ pub async fn show_ingress_form(
|
||||
return Ok(Redirect::to("/").into_response());
|
||||
}
|
||||
|
||||
let output = render_template("ingress_form.html", PageData {}, state.templates.clone())
|
||||
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?;
|
||||
let output = render_template("ingress_form.html", {}, state.templates.clone())?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
@@ -49,15 +43,47 @@ pub struct IngressParams {
|
||||
pub files: Vec<FieldData<NamedTempFile>>,
|
||||
}
|
||||
|
||||
page_data!(IngressFormData, "ingress_form.html", {
|
||||
instructions: String,
|
||||
content: String,
|
||||
});
|
||||
|
||||
pub async fn process_ingress_form(
|
||||
State(state): State<AppState>,
|
||||
auth: AuthSession<User, String, SessionSurrealPool<Any>, Surreal<Any>>,
|
||||
TypedMultipart(input): TypedMultipart<IngressParams>,
|
||||
) -> Result<impl IntoResponse, HtmlError> {
|
||||
let user = match auth.current_user {
|
||||
Some(user) => user,
|
||||
None => return Ok(Redirect::to("/").into_response()),
|
||||
};
|
||||
let user = auth.current_user.ok_or_else(|| {
|
||||
AppError::Auth("You must be signed in".to_string()).with_template(state.templates.clone())
|
||||
})?;
|
||||
// let user = match auth.current_user {
|
||||
// Some(user) => user,
|
||||
// None => {
|
||||
// return Err(HtmlError::new(
|
||||
// AppError::Auth("You must be signed in".to_string()),
|
||||
// state.templates,
|
||||
// ))
|
||||
// }
|
||||
// };
|
||||
|
||||
if input.content.clone().is_some_and(|c| c.len() < 2) && input.files.is_empty() {
|
||||
let output = render_template(
|
||||
IngressFormData::template_name(),
|
||||
IngressFormData {
|
||||
instructions: input.instructions.clone(),
|
||||
content: input.content.clone().unwrap(),
|
||||
},
|
||||
state.templates.clone(),
|
||||
)?;
|
||||
|
||||
return Ok(output.into_response());
|
||||
|
||||
// return Ok((
|
||||
// StatusCode::UNAUTHORIZED,
|
||||
// Html("Invalid input, make sure you fill in either content or add files"),
|
||||
// )
|
||||
// .into_response());
|
||||
}
|
||||
|
||||
info!("{:?}", input);
|
||||
|
||||
@@ -88,5 +114,8 @@ pub async fn process_ingress_form(
|
||||
.map_err(AppError::from)
|
||||
.map_err(|e| HtmlError::new(e, state.templates.clone()))?;
|
||||
|
||||
Ok(Html("SuccessBRO!").into_response())
|
||||
Ok(Html(
|
||||
"<a class='btn btn-primary' hx-get='/ingress-form' hx-swap='outerHTML'>Add Content</a>",
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
error::{AppError, HtmlError},
|
||||
error::HtmlError,
|
||||
page_data,
|
||||
server::AppState,
|
||||
storage::types::{job::Job, user::User},
|
||||
@@ -35,8 +35,7 @@ pub async fn show_queue_tasks(
|
||||
ShowQueueTasks::template_name(),
|
||||
ShowQueueTasks { jobs, user },
|
||||
state.templates.clone(),
|
||||
)
|
||||
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?;
|
||||
)?;
|
||||
|
||||
Ok(rendered.into_response())
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ use std::sync::Arc;
|
||||
use axum::response::Html;
|
||||
use minijinja_autoreload::AutoReloader;
|
||||
|
||||
use crate::error::{HtmlError, IntoHtmlError};
|
||||
|
||||
pub mod account;
|
||||
pub mod documentation;
|
||||
pub mod gdpr;
|
||||
pub mod index;
|
||||
pub mod ingress;
|
||||
pub mod ingress_form;
|
||||
pub mod ingress_tasks;
|
||||
pub mod privacy_policy;
|
||||
pub mod search_result;
|
||||
@@ -19,21 +21,26 @@ pub trait PageData {
|
||||
fn template_name() -> &'static str;
|
||||
}
|
||||
|
||||
// Helper function for render_template
|
||||
pub fn render_template<T>(
|
||||
template_name: &str,
|
||||
context: T,
|
||||
templates: Arc<AutoReloader>,
|
||||
) -> Result<Html<String>, minijinja::Error>
|
||||
) -> Result<Html<String>, HtmlError>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
let env = templates.acquire_env()?;
|
||||
let tmpl = env.get_template(template_name)?;
|
||||
|
||||
let env = templates
|
||||
.acquire_env()
|
||||
.map_err(|e| e.with_template(templates.clone()))?;
|
||||
let tmpl = env
|
||||
.get_template(template_name)
|
||||
.map_err(|e| e.with_template(templates.clone()))?;
|
||||
let context = minijinja::Value::from_serialize(&context);
|
||||
let output = tmpl.render(context)?;
|
||||
|
||||
Ok(output.into())
|
||||
let output = tmpl
|
||||
.render(context)
|
||||
.map_err(|e| e.with_template(templates.clone()))?;
|
||||
Ok(Html(output))
|
||||
}
|
||||
|
||||
pub fn render_block<T>(
|
||||
@@ -41,15 +48,23 @@ pub fn render_block<T>(
|
||||
block: &str,
|
||||
context: T,
|
||||
templates: Arc<AutoReloader>,
|
||||
) -> Result<Html<String>, minijinja::Error>
|
||||
) -> Result<Html<String>, HtmlError>
|
||||
where
|
||||
T: serde::Serialize,
|
||||
{
|
||||
let env = templates.acquire_env()?;
|
||||
let tmpl = env.get_template(template_name)?;
|
||||
let env = templates
|
||||
.acquire_env()
|
||||
.map_err(|e| e.with_template(templates.clone()))?;
|
||||
let tmpl = env
|
||||
.get_template(template_name)
|
||||
.map_err(|e| e.with_template(templates.clone()))?;
|
||||
|
||||
let context = minijinja::Value::from_serialize(&context);
|
||||
let output = tmpl.eval_to_state(context)?.render_block(block)?;
|
||||
let output = tmpl
|
||||
.eval_to_state(context)
|
||||
.map_err(|e| e.with_template(templates.clone()))?
|
||||
.render_block(block)
|
||||
.map_err(|e| e.with_template(templates.clone()))?;
|
||||
|
||||
Ok(output.into())
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ pub async fn show_privacy_policy(
|
||||
user: auth.current_user,
|
||||
},
|
||||
state.templates.clone(),
|
||||
)
|
||||
.map_err(|e| HtmlError::from_template_error(e, state.templates.clone()))?;
|
||||
)?;
|
||||
|
||||
Ok(output.into_response())
|
||||
}
|
||||
|
||||
@@ -9,12 +9,7 @@ use axum_session_auth::AuthSession;
|
||||
use axum_session_surreal::SessionSurrealPool;
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
|
||||
use crate::{
|
||||
error::{AppError, HtmlError},
|
||||
page_data,
|
||||
server::AppState,
|
||||
storage::types::user::User,
|
||||
};
|
||||
use crate::{error::HtmlError, page_data, server::AppState, storage::types::user::User};
|
||||
|
||||
use super::{render_block, render_template};
|
||||
|
||||
@@ -41,14 +36,12 @@ pub async fn show_signin_form(
|
||||
"body",
|
||||
ShowSignInForm {},
|
||||
state.templates.clone(),
|
||||
)
|
||||
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?,
|
||||
)?,
|
||||
false => render_template(
|
||||
ShowSignInForm::template_name(),
|
||||
ShowSignInForm {},
|
||||
state.templates.clone(),
|
||||
)
|
||||
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?,
|
||||
)?,
|
||||
};
|
||||
|
||||
Ok(output.into_response())
|
||||
|
||||
@@ -11,11 +11,7 @@ use serde::{Deserialize, Serialize};
|
||||
use surrealdb::{engine::any::Any, Surreal};
|
||||
use tracing::info;
|
||||
|
||||
use crate::{
|
||||
error::{AppError, HtmlError},
|
||||
server::AppState,
|
||||
storage::types::user::User,
|
||||
};
|
||||
use crate::{error::HtmlError, server::AppState, storage::types::user::User};
|
||||
|
||||
use super::{render_block, render_template};
|
||||
|
||||
@@ -44,14 +40,12 @@ pub async fn show_signup_form(
|
||||
"body",
|
||||
PageData {},
|
||||
state.templates.clone(),
|
||||
)
|
||||
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?,
|
||||
)?,
|
||||
false => render_template(
|
||||
"auth/signup_form.html",
|
||||
PageData {},
|
||||
state.templates.clone(),
|
||||
)
|
||||
.map_err(|e| HtmlError::new(AppError::from(e), state.templates.clone()))?,
|
||||
)?,
|
||||
};
|
||||
|
||||
Ok(output.into_response())
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions - Modified button to link to ingress form -->
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card bg-base-100 shadow-xl mt-4">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Quick Actions</h2>
|
||||
<div class="flex gap-4">
|
||||
<a class="btn btn-primary" href="/ingress" hx-boost="true">Add Content</a>
|
||||
<a class="btn btn-primary" hx-get="/ingress-form" hx-swap="outerHTML">Add Content</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
{% extends "body_base.html" %}
|
||||
{% block main %}
|
||||
<div class="flex justify-center grow mt-2 sm:mt-4">
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<form class="space-y-2" hx-post="/ingress-form" enctype="multipart/form-data">
|
||||
<h1 class="text-2xl">Add content to the database </h1>
|
||||
<form class="space-y-2" hx-post="/ingress" enctype="multipart/form-data">
|
||||
<label class="label label-text">Instructions</label>
|
||||
<textarea name="instructions" class="textarea w-full input-bordered"
|
||||
placeholder="Enter instructions for the AI here, help it understand what its seeing or how it should relate to the database"></textarea>
|
||||
<label class="label label-text">Content (optional)</label>
|
||||
<textarea name="content" class="textarea w-full input-bordered" placeholder="Additional content"></textarea>
|
||||
<label class="label label-text">Category</label>
|
||||
<input type="text" name="category" class="input input-bordered" placeholder="Category for ingress">
|
||||
<label class="label label-text">Instructions</label>
|
||||
<textarea name="instructions" class="textarea w-full input-bordered"
|
||||
placeholder="Enter instructions for the AI here, help it understand what its seeing or how it should relate to the database">{{instructions
|
||||
}}</textarea>
|
||||
<label class="label label-text">Content (optional)</label>
|
||||
<textarea name="content" class="textarea w-full input-bordered" placeholder="Additional content">{{content
|
||||
}}</textarea>
|
||||
<label class="label label-text">Category</label>
|
||||
<input type="text" name="category" class="input input-bordered" placeholder="Category for ingress">
|
||||
|
||||
<label class="label label-text">Files</label>
|
||||
<input type="file" name="files" multiple class="file-input file-input-bordered" />
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
<div id="ingress-result"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
<label class="label label-text">Files</label>
|
||||
<input type="file" name="files" multiple class="file-input file-input-bordered" />
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</form>
|
||||
<div id="ingress-result"></div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user