use askama::Template;
use axum::{
Form, Router,
extract::State,
http::{HeaderMap, StatusCode, Uri},
response::{Html, IntoResponse, Redirect, Response},
routing::{get, post},
};
use ocre::{Ctx, Error, FieldError, Flash, Result, Session, render};
use serde::Deserialize;
use crate::{
auth::{CurrentUser, sign_in, sign_out},
auth_api::throttle,
confirmations::send_confirmation,
models::user::{self, NewUser, User},
};
pub fn routes() -> Router<Ctx> {
Router::new()
.route("/signup", get(new).post(create))
.route("/account", get(show))
.route("/account/delete", post(destroy))
}
#[derive(Template)]
#[template(path = "auth/signup.html")]
struct SignupView {
email: String,
errors: Vec<FieldError>,
}
#[derive(Template)]
#[template(path = "auth/account.html")]
struct AccountView {
flash: Flash,
user: User,
}
#[derive(Clone, Default, Deserialize)]
#[serde(default)]
pub struct DeleteForm {
pub confirmation: String,
}
async fn new() -> Result<Html<String>> {
render(&SignupView { email: String::new(), errors: vec![] })
}
async fn create(
State(ctx): State<Ctx>,
session: Session,
headers: HeaderMap,
uri: Uri,
Form(new): Form<NewUser>,
) -> Result<Response> {
throttle(&ctx, &headers, "signup").await?;
let email = new.email.clone();
match user::create(&ctx, new).await {
Ok(user) => {
send_confirmation(&ctx, &uri, &user).await?;
let next = sign_in(&ctx, &session, &headers, &user, false).await?;
session.flash("notice", "Welcome! Your account is ready. Check your email to confirm your address.")?;
Ok(Redirect::to(&next).into_response())
}
Err(Error::Invalid(errors)) => {
Ok((StatusCode::UNPROCESSABLE_ENTITY, render(&SignupView { email, errors })?).into_response())
}
Err(err) => Err(err),
}
}
async fn show(CurrentUser(user): CurrentUser, flash: Flash) -> Result<Html<String>> {
render(&AccountView { flash, user })
}
async fn destroy(
State(ctx): State<Ctx>,
CurrentUser(user): CurrentUser,
session: Session,
headers: HeaderMap,
Form(form): Form<DeleteForm>,
) -> Result<Redirect> {
throttle(&ctx, &headers, "account_delete").await?;
if !user::deletion_confirmed(&user, &form.confirmation).await? {
let what = if user.has_password() { "password" } else { "email address" };
session.flash("alert", format!("That is not your {what}: your account was not deleted."))?;
return Ok(Redirect::to("/account"));
}
sign_out(&ctx, &session).await?;
user::delete(&ctx, user.id).await?;
session.flash("notice", "Your account has been deleted.")?;
Ok(Redirect::to("/"))
}