Migrate to askama

This commit is contained in:
Mo 2022-11-01 20:45:06 +01:00
parent 5b007a3220
commit 14c418dacc
9 changed files with 62 additions and 60 deletions

View file

@ -8,10 +8,15 @@ license-file = "LICENSE.txt"
[dependencies] [dependencies]
anyhow = "1.0" anyhow = "1.0"
axum = "0.5" askama = { git = "https://github.com/djc/askama.git" }
askama_axum = { git = "https://github.com/djc/askama.git", package = "askama_axum" }
axum = { version = "0.5", default-features = false, features = [
"http1",
"query",
"form",
] }
captcha = "0.0.9" captcha = "0.0.9"
lettre = "0.10" lettre = "0.10"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
tera = "1.17"
tokio = { version = "1.21", features = ["full"] } tokio = { version = "1.21", features = ["full"] }

16
src/errors.rs Normal file
View file

@ -0,0 +1,16 @@
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
pub struct AppError(anyhow::Error);
impl IntoResponse for AppError {
fn into_response(self) -> Response {
StatusCode::BAD_REQUEST.into_response()
}
}
impl From<anyhow::Error> for AppError {
fn from(err: anyhow::Error) -> Self {
Self(err.into())
}
}

View file

@ -1,21 +1,19 @@
mod captcha_solutions; mod captcha_solutions;
mod config; mod config;
mod context; mod errors;
mod forms; mod forms;
mod mailer; mod mailer;
mod routes; mod routes;
mod templates;
use anyhow::{Context, Result}; use anyhow::Result;
use axum::extract::Extension; use axum::extract::Extension;
use axum::routing::{get, post}; use axum::routing::{get, post};
use axum::{Router, Server}; use axum::{Router, Server};
use std::process; use std::process;
use std::sync::Arc; use std::sync::Arc;
use tera::Tera;
async fn init() -> Result<()> { async fn init() -> Result<()> {
let tera = Arc::new(Tera::new("templates/*").context("Failed to parse templates!")?);
let mut config = config::Config::new()?; let mut config = config::Config::new()?;
let path_prefix = config.path_prefix.clone(); let path_prefix = config.path_prefix.clone();
let mailer = Arc::new(mailer::Mailer::new(&mut config)?); let mailer = Arc::new(mailer::Mailer::new(&mut config)?);
@ -28,8 +26,7 @@ async fn init() -> Result<()> {
.route("/success", get(routes::success)) .route("/success", get(routes::success))
.layer(Extension(config)) .layer(Extension(config))
.layer(Extension(mailer)) .layer(Extension(mailer))
.layer(Extension(captcha_solutions)) .layer(Extension(captcha_solutions));
.layer(Extension(tera));
let app = Router::new().nest(&path_prefix, routes); let app = Router::new().nest(&path_prefix, routes);

View file

@ -1,30 +1,12 @@
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use askama_axum::IntoResponse;
use axum::extract::{Extension, Form, Query}; use axum::extract::{Extension, Form, Query};
use axum::http::StatusCode; use axum::response::Response;
use axum::response::{Html, IntoResponse, Response};
use serde::Deserialize; use serde::Deserialize;
use std::mem; use std::mem;
use std::sync::Arc; use std::sync::Arc;
use tera::Tera;
use crate::{captcha_solutions, config, context, forms, mailer}; use crate::{captcha_solutions, config, errors, forms, mailer, templates};
pub struct AppError(anyhow::Error);
impl IntoResponse for AppError {
fn into_response(self) -> Response {
StatusCode::BAD_REQUEST.into_response()
}
}
impl<E> From<E> for AppError
where
E: Into<anyhow::Error>,
{
fn from(err: E) -> Self {
Self(err.into())
}
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct IndexParams { pub struct IndexParams {
@ -37,16 +19,15 @@ pub struct IndexParams {
pub async fn index( pub async fn index(
Query(params): Query<IndexParams>, Query(params): Query<IndexParams>,
engine: Extension<Arc<Tera>>,
config: Extension<Arc<config::Config>>, config: Extension<Arc<config::Config>>,
captcha_solutions: Extension<Arc<captcha_solutions::SharedCaptchaSolutions>>, captcha_solutions: Extension<Arc<captcha_solutions::SharedCaptchaSolutions>>,
) -> Result<Response, AppError> { ) -> Result<Response, errors::AppError> {
let captcha = captcha::by_name(captcha::Difficulty::Easy, captcha::CaptchaName::Lucy); let captcha = captcha::by_name(captcha::Difficulty::Easy, captcha::CaptchaName::Lucy);
let captcha_base64 = captcha.as_base64().context("Failed to create a captcha!")?; let captcha_base64 = captcha.as_base64().context("Failed to create a captcha!")?;
let id = captcha_solutions.store_solution(&captcha.chars_as_string()); let id = captcha_solutions.store_solution(&captcha.chars_as_string());
let form_context = context::ContactForm { let template = templates::ContactForm {
path_prefix: &config.path_prefix, path_prefix: &config.path_prefix,
was_validated: params.was_validated.unwrap_or(false), was_validated: params.was_validated.unwrap_or(false),
id, id,
@ -57,20 +38,14 @@ pub async fn index(
captcha: &captcha_base64, captcha: &captcha_base64,
}; };
let html = engine.render( Ok(template.into_response())
"form.html.tera",
&tera::Context::from_serialize(&form_context)?,
)?;
Ok(Html(html).into_response())
} }
async fn back_to_index( async fn back_to_index(
mut form: forms::ContactForm, mut form: forms::ContactForm,
engine: Extension<Arc<Tera>>,
config: Extension<Arc<config::Config>>, config: Extension<Arc<config::Config>>,
captcha_solutions: Extension<Arc<captcha_solutions::SharedCaptchaSolutions>>, captcha_solutions: Extension<Arc<captcha_solutions::SharedCaptchaSolutions>>,
) -> Result<Response, AppError> { ) -> Result<Response, errors::AppError> {
let name = mem::take(&mut form.name); let name = mem::take(&mut form.name);
let email = mem::take(&mut form.email); let email = mem::take(&mut form.email);
let telefon = mem::take(&mut form.telefon); let telefon = mem::take(&mut form.telefon);
@ -84,32 +59,34 @@ async fn back_to_index(
message: Some(message), message: Some(message),
}); });
index(params, engine, config, captcha_solutions).await index(params, config, captcha_solutions).await
} }
pub async fn submit( pub async fn submit(
Form(form): Form<forms::ContactForm>, Form(form): Form<forms::ContactForm>,
engine: Extension<Arc<Tera>>,
config: Extension<Arc<config::Config>>, config: Extension<Arc<config::Config>>,
captcha_solutions: Extension<Arc<captcha_solutions::SharedCaptchaSolutions>>, captcha_solutions: Extension<Arc<captcha_solutions::SharedCaptchaSolutions>>,
mailer: Extension<Arc<mailer::Mailer>>, mailer: Extension<Arc<mailer::Mailer>>,
) -> Result<Response, AppError> { ) -> Result<Response, errors::AppError> {
if !captcha_solutions.check_answer(form.id, &form.captcha_answer) { if !captcha_solutions.check_answer(form.id, &form.captcha_answer) {
return back_to_index(form, engine, config, captcha_solutions).await; return back_to_index(form, config, captcha_solutions).await;
} }
match mailer.send(&form.name, &form.email, &form.telefon, &form.message) { match mailer.send(&form.name, &form.email, &form.telefon, &form.message) {
Ok(_) => (), Ok(_) => (),
Err(_) => { Err(_) => {
return back_to_index(form, engine, config, captcha_solutions).await; return back_to_index(form, config, captcha_solutions).await;
} }
} }
success(engine).await success().await
} }
pub async fn success(engine: Extension<Arc<Tera>>) -> Result<Response, AppError> { pub async fn success() -> Result<Response, errors::AppError> {
let html = engine.render("success.html.tera", &tera::Context::new())?; let template = templates::Success {
message:
"Ihre Anfrage wurde erfolgreich übermittelt! Wir melden uns bald bei Ihnen zurück.",
};
Ok(Html(html).into_response()) Ok(template.into_response())
} }

View file

@ -1,6 +1,7 @@
use serde::Serialize; use askama::Template;
#[derive(Serialize)] #[derive(Template)]
#[template(path = "contact_form.askama.html")]
pub struct ContactForm<'a> { pub struct ContactForm<'a> {
pub path_prefix: &'a str, pub path_prefix: &'a str,
pub was_validated: bool, pub was_validated: bool,
@ -11,3 +12,9 @@ pub struct ContactForm<'a> {
pub message: &'a str, pub message: &'a str,
pub captcha: &'a str, pub captcha: &'a str,
} }
#[derive(Template)]
#[template(path = "success.askama.html")]
pub struct Success<'a> {
pub message: &'a str,
}

View file

@ -1,4 +1,4 @@
{% extends "base.html.tera" %} {% extends "base.askama.html" %}
{% block body %} {% block body %}
<p> <p>

View file

@ -0,0 +1,7 @@
{% extends "base.askama.html" %}
{% block body %}
<div class="alert alert-success" role="alert">
{{ message }}
</div>
{% endblock %}

View file

@ -1,7 +0,0 @@
{% extends "base.html.tera" %}
{% block body %}
<div class="alert alert-success" role="alert">
Ihre Anfrage wurde erfolgreich übermittelt! Wir melden uns bald bei Ihnen zurück.
</div>
{% endblock %}