Migrate to Axum
This commit is contained in:
parent
c25d431cf3
commit
5b007a3220
8 changed files with 124 additions and 96 deletions
|
@ -8,9 +8,10 @@ license-file = "LICENSE.txt"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
axum = "0.5"
|
||||||
captcha = "0.0.9"
|
captcha = "0.0.9"
|
||||||
lettre = "0.10"
|
lettre = "0.10"
|
||||||
rocket = "0.5.0-rc.2"
|
|
||||||
rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["tera"] }
|
|
||||||
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"] }
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
[default]
|
|
||||||
template_dir = "templates"
|
|
||||||
|
|
||||||
[release]
|
|
||||||
address = "0.0.0.0"
|
|
||||||
port = 80
|
|
|
@ -1,7 +1,7 @@
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ContactFormContext<'a> {
|
pub struct ContactForm<'a> {
|
||||||
pub path_prefix: &'a str,
|
pub path_prefix: &'a str,
|
||||||
pub was_validated: bool,
|
pub was_validated: bool,
|
||||||
pub id: u16,
|
pub id: u16,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use rocket::form::FromForm;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(Deserialize)]
|
||||||
pub struct ContactForm {
|
pub struct ContactForm {
|
||||||
pub id: u16,
|
pub id: u16,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
49
src/main.rs
49
src/main.rs
|
@ -5,30 +5,45 @@ mod forms;
|
||||||
mod mailer;
|
mod mailer;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{Context, Result};
|
||||||
use rocket::{Build, Rocket};
|
use axum::extract::Extension;
|
||||||
use rocket_dyn_templates::Template;
|
use axum::routing::{get, post};
|
||||||
|
use axum::{Router, Server};
|
||||||
use std::process;
|
use std::process;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tera::Tera;
|
||||||
|
|
||||||
|
async fn init() -> Result<()> {
|
||||||
|
let tera = Arc::new(Tera::new("templates/*").context("Failed to parse templates!")?);
|
||||||
|
|
||||||
fn init() -> Result<Rocket<Build>> {
|
|
||||||
let mut config = config::Config::new()?;
|
let mut config = config::Config::new()?;
|
||||||
|
let path_prefix = config.path_prefix.clone();
|
||||||
|
let mailer = Arc::new(mailer::Mailer::new(&mut config)?);
|
||||||
|
let config = Arc::new(config);
|
||||||
|
let captcha_solutions = Arc::new(captcha_solutions::SharedCaptchaSolutions::new());
|
||||||
|
|
||||||
let rocket = rocket::build()
|
let routes = Router::new()
|
||||||
.mount(
|
.route("/", get(routes::index))
|
||||||
&config.path_prefix,
|
.route("/submit", post(routes::submit))
|
||||||
rocket::routes![routes::index, routes::submit, routes::success],
|
.route("/success", get(routes::success))
|
||||||
)
|
.layer(Extension(config))
|
||||||
.manage(captcha_solutions::SharedCaptchaSolutions::new())
|
.layer(Extension(mailer))
|
||||||
.manage(mailer::Mailer::new(&mut config)?)
|
.layer(Extension(captcha_solutions))
|
||||||
.manage(config)
|
.layer(Extension(tera));
|
||||||
.attach(Template::fairing());
|
|
||||||
|
|
||||||
Ok(rocket)
|
let app = Router::new().nest(&path_prefix, routes);
|
||||||
|
|
||||||
|
Server::bind(&([127, 0, 0, 1], 8080).into())
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rocket::launch]
|
#[tokio::main]
|
||||||
fn rocket() -> _ {
|
async fn main() {
|
||||||
init().unwrap_or_else(|e| {
|
init().await.unwrap_or_else(|e| {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
})
|
})
|
||||||
|
|
152
src/routes.rs
152
src/routes.rs
|
@ -1,97 +1,115 @@
|
||||||
use rocket::form::{Form, Strict};
|
use anyhow::{Context, Result};
|
||||||
use rocket::response::status::BadRequest;
|
use axum::extract::{Extension, Form, Query};
|
||||||
use rocket::response::Redirect;
|
use axum::http::StatusCode;
|
||||||
use rocket::{get, post, uri, State};
|
use axum::response::{Html, IntoResponse, Response};
|
||||||
use rocket_dyn_templates::{context, Template};
|
use serde::Deserialize;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tera::Tera;
|
||||||
|
|
||||||
use crate::captcha_solutions;
|
use crate::{captcha_solutions, config, context, forms, mailer};
|
||||||
use crate::config;
|
|
||||||
use crate::context;
|
|
||||||
use crate::forms;
|
|
||||||
use crate::mailer;
|
|
||||||
|
|
||||||
#[get("/?<was_validated>&<name>&<email>&<telefon>&<message>")]
|
pub struct AppError(anyhow::Error);
|
||||||
pub fn index(
|
|
||||||
|
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)]
|
||||||
|
pub struct IndexParams {
|
||||||
was_validated: Option<bool>,
|
was_validated: Option<bool>,
|
||||||
name: Option<&str>,
|
name: Option<String>,
|
||||||
email: Option<&str>,
|
email: Option<String>,
|
||||||
telefon: Option<&str>,
|
telefon: Option<String>,
|
||||||
message: Option<&str>,
|
message: Option<String>,
|
||||||
config: &State<config::Config>,
|
}
|
||||||
captcha_solutions: &State<captcha_solutions::SharedCaptchaSolutions>,
|
|
||||||
) -> Result<Template, BadRequest<()>> {
|
pub async fn index(
|
||||||
|
Query(params): Query<IndexParams>,
|
||||||
|
engine: Extension<Arc<Tera>>,
|
||||||
|
config: Extension<Arc<config::Config>>,
|
||||||
|
captcha_solutions: Extension<Arc<captcha_solutions::SharedCaptchaSolutions>>,
|
||||||
|
) -> Result<Response, 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 = match captcha.as_base64() {
|
let captcha_base64 = captcha.as_base64().context("Failed to create a captcha!")?;
|
||||||
Some(s) => s,
|
|
||||||
None => return Err(BadRequest(None)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let id = captcha_solutions.store_solution(&captcha.chars_as_string());
|
let id = captcha_solutions.store_solution(&captcha.chars_as_string());
|
||||||
|
|
||||||
let form_context = context::ContactFormContext {
|
let form_context = context::ContactForm {
|
||||||
path_prefix: &config.path_prefix,
|
path_prefix: &config.path_prefix,
|
||||||
was_validated: was_validated.unwrap_or(false),
|
was_validated: params.was_validated.unwrap_or(false),
|
||||||
id,
|
id,
|
||||||
name: name.unwrap_or(""),
|
name: ¶ms.name.unwrap_or_default(),
|
||||||
email: email.unwrap_or(""),
|
email: ¶ms.email.unwrap_or_default(),
|
||||||
telefon: telefon.unwrap_or(""),
|
telefon: ¶ms.telefon.unwrap_or_default(),
|
||||||
message: message.unwrap_or(""),
|
message: ¶ms.message.unwrap_or_default(),
|
||||||
captcha: &captcha_base64,
|
captcha: &captcha_base64,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Template::render("form", form_context))
|
let html = engine.render(
|
||||||
|
"form.html.tera",
|
||||||
|
&tera::Context::from_serialize(&form_context)?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(Html(html).into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/submit", data = "<form>")]
|
async fn back_to_index(
|
||||||
pub fn submit(
|
mut form: forms::ContactForm,
|
||||||
mut form: Form<Strict<forms::ContactForm>>,
|
engine: Extension<Arc<Tera>>,
|
||||||
config: &State<config::Config>,
|
config: Extension<Arc<config::Config>>,
|
||||||
captcha_solutions: &State<captcha_solutions::SharedCaptchaSolutions>,
|
captcha_solutions: Extension<Arc<captcha_solutions::SharedCaptchaSolutions>>,
|
||||||
mailer: &State<mailer::Mailer>,
|
) -> Result<Response, AppError> {
|
||||||
) -> Redirect {
|
|
||||||
let path_prefix = config.path_prefix.clone();
|
|
||||||
|
|
||||||
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);
|
||||||
let message = mem::take(&mut form.message);
|
let message = mem::take(&mut form.message);
|
||||||
|
|
||||||
if !captcha_solutions.check_answer(form.id, &form.captcha_answer) {
|
let params = Query(IndexParams {
|
||||||
return Redirect::to(
|
was_validated: Some(true),
|
||||||
path_prefix
|
name: Some(name),
|
||||||
+ &uri!(index(
|
email: Some(email),
|
||||||
Some(true),
|
telefon: Some(telefon),
|
||||||
Some(&name),
|
message: Some(message),
|
||||||
Some(&email),
|
});
|
||||||
Some(&telefon),
|
|
||||||
Some(&message)
|
index(params, engine, config, captcha_solutions).await
|
||||||
))
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match mailer.send(&name, &email, &telefon, &message) {
|
pub async fn submit(
|
||||||
|
Form(form): Form<forms::ContactForm>,
|
||||||
|
engine: Extension<Arc<Tera>>,
|
||||||
|
config: Extension<Arc<config::Config>>,
|
||||||
|
captcha_solutions: Extension<Arc<captcha_solutions::SharedCaptchaSolutions>>,
|
||||||
|
mailer: Extension<Arc<mailer::Mailer>>,
|
||||||
|
) -> Result<Response, AppError> {
|
||||||
|
if !captcha_solutions.check_answer(form.id, &form.captcha_answer) {
|
||||||
|
return back_to_index(form, engine, config, captcha_solutions).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
match mailer.send(&form.name, &form.email, &form.telefon, &form.message) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Redirect::to(
|
return back_to_index(form, engine, config, captcha_solutions).await;
|
||||||
path_prefix
|
|
||||||
+ &uri!(index(
|
|
||||||
Some(true),
|
|
||||||
Some(&name),
|
|
||||||
Some(&email),
|
|
||||||
Some(&telefon),
|
|
||||||
Some(&message)
|
|
||||||
))
|
|
||||||
.to_string(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Redirect::to(path_prefix + &uri!(success()).to_string())
|
success(engine).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/success")]
|
pub async fn success(engine: Extension<Arc<Tera>>) -> Result<Response, AppError> {
|
||||||
pub fn success() -> Template {
|
let html = engine.render("success.html.tera", &tera::Context::new())?;
|
||||||
Template::render("success", context! {})
|
|
||||||
|
Ok(Html(html).into_response())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "base" %}
|
{% extends "base.html.tera" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "base" %}
|
{% extends "base.html.tera" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="alert alert-success" role="alert">
|
<div class="alert alert-success" role="alert">
|
||||||
|
|
Loading…
Reference in a new issue